File size: 58,004 Bytes
bae2115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ad78c34
bae2115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1eabbf4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
# app.py (Company Portal)
import os
import pandas as  pd
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import json
import io           
import numpy as np
import hashlib
from datetime import datetime

year = datetime.now().year
import re           
from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_file, flash, make_response
import google.generativeai as genai
from datetime import datetime
from supabase import create_client, Client
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)
app.secret_key = os.getenv("COMPANY_PORTAL_SECRET_KEY", os.urandom(24))

# --- Supabase Client Initialization ---
supabase_url = os.getenv("SUPABASE_URL")
supabase_key = os.getenv("SUPABASE_KEY") 

if not supabase_url or not supabase_key:
    print("CRITICAL ERROR: SUPABASE_URL and SUPABASE_KEY environment variables are required.")
    supabase: Client = None
else:
    try:
        supabase: Client = create_client(supabase_url, supabase_key)
        print("Supabase client initialized successfully for Company Portal.")
    except Exception as e:
        print(f"CRITICAL ERROR: Error initializing Supabase client for Company Portal: {e}")
        supabase = None

# --- Gemini API Configuration ---
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
HAS_GEMINI = False
if GEMINI_API_KEY:
    try:
        genai.configure(api_key=GEMINI_API_KEY)
        HAS_GEMINI = True
        print("Gemini API Configured.")
    except Exception as e:
        print(f"Warning: Error configuring Gemini API: {e}. AI Insights & NL Query may fail.")
else:
    print("Warning: GEMINI_API_KEY not found in .env file. AI Insights & NL Query will not work.")

# --- Helper Functions ---

def verify_password(stored_hash, password):
    """Verifies a password against a stored SHA256 hash."""
    input_hash = hashlib.sha256(password.encode()).hexdigest()
    return input_hash == stored_hash

def load_student_data():
    """Loads all student data from the Supabase 'students' table."""
    if not supabase:
        flash("Database connection error. Cannot load student data.", "danger")
        return pd.DataFrame()

    try:
        response = supabase.from_('companies').select('*').execute()
        if response.data:
            df = pd.DataFrame(response.data)
            numeric_cols = ['cgpa', 'backlogs', 'typing_speed']
            for col in numeric_cols:
                if col in df.columns:
                    # Coerce errors to NaN, then handle NaN later if needed
                    df[col] = pd.to_numeric(df[col], errors='coerce')
            if 'backlogs' in df.columns:
                # Fill NaN with 0 before converting to int
                df['backlogs'] = df['backlogs'].fillna(0).astype(int)
            # Example of how you might handle array columns if stored as strings (adjust based on DB schema)
            # list_cols = ['programming_languages', 'tools_technologies', 'soft_skills', 'preferred_roles', 'certifications']
            # for col in list_cols:
            #     if col in df.columns and df[col].dtype == 'object':
            #         # Attempt to parse comma-separated string or already list-like structure
            #         df[col] = df[col].apply(lambda x: [i.strip() for i in str(x).split(',')] if pd.notna(x) and isinstance(x, str) else x)

            print(f"Loaded {len(df)} student records.") # Debug
            return df
        else:
            print("No student data found in Supabase.") # Debug
            return pd.DataFrame()
    except Exception as e:
        print(f"Error loading student data from Supabase: {e}")
        flash(f"Error loading student data: {e}", "danger")
        return pd.DataFrame()


def send_job_email(job_data):
    """Sends job posting details via email."""
    try:
        # Email configuration from environment variables
        smtp_server = os.getenv("SMTP_SERVER")
        smtp_port = int(os.getenv("SMTP_PORT", 0))
        smtp_username = os.getenv("SMTP_USERNAME")
        smtp_password = os.getenv("SMTP_PASSWORD")
        if not all([smtp_server, smtp_port, smtp_username, smtp_password]):
            raise RuntimeError("SMTP credentials are missing or invalid in .env")
        # Recipient email
        recipient_email ="pranit.chilbule221@vit.edu"

        # Create message
        msg = MIMEMultipart()
        msg['From'] = smtp_username
        msg['To'] = recipient_email
        msg['Subject'] = f"New Job Posting: {job_data['title']}"

        # Email body
        body = f"""
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <title>New Job Posting</title>
        </head>
        <body style="margin:0;padding:0;background-color:#f4f4f4;font-family:Arial,sans-serif;">
          <!--[if mso]>
            <style>
              body, table, td {{
                font-family: Arial, sans-serif !important;
              }}
            </style>
          <![endif]-->

          <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
            <tr>
              <td align="center" style="padding:20px 0;">
                <table width="600" cellpadding="0" cellspacing="0" role="presentation"
                       style="background-color:#ffffff;border-radius:8px;overflow:hidden;
                              box-shadow:0 2px 6px rgba(0,0,0,0.1);">

                  <!-- Header -->
                  <tr>
                    <td style="background-color:#004aad;padding:20px;text-align:center;color:#ffffff;">
                      <h1 style="margin:0;font-size:24px;line-height:28px;">New Job Posting</h1>
                    </td>
                  </tr>

                  <!-- Body content -->
                  <tr>
                    <td style="padding:30px;">
                      <p style="margin:0 0 16px;font-size:16px;line-height:22px;">
                        Hello Team,
                      </p>

                      <table width="100%" cellpadding="0" cellspacing="0" role="presentation"
                             style="margin-bottom:24px;">
                        <tr>
                          <td style="padding:8px 0;font-weight:bold;width:30%;">Title:</td>
                          <td style="padding:8px 0;">{job_data['title']}</td>
                        </tr>
                        <tr style="background-color:#f9f9f9;">
                          <td style="padding:8px 0;font-weight:bold;">Location:</td>
                          <td style="padding:8px 0;">{job_data['location']}</td>
                        </tr>
                        <tr>
                          <td style="padding:8px 0;font-weight:bold;">Required Skills:</td>
                          <td style="padding:8px 0;">{', '.join(job_data['required_skills'])}</td>
                        </tr>
                        <tr style="background-color:#f9f9f9;">
                          <td style="padding:8px 0;font-weight:bold;">Minimum CGPA:</td>
                          <td style="padding:8px 0;">
                            {job_data['min_cgpa'] if job_data['min_cgpa'] else 'Not specified'}
                          </td>
                        </tr>
                        <tr>
                          <td style="padding:8px 0;font-weight:bold;">Experience Level:</td>
                          <td style="padding:8px 0;">{job_data['experience_level']} year(s)</td>
                        </tr>
                      </table>

                      <p style="margin:0 0 8px;font-weight:bold;font-size:16px;">Description:</p>
                      <p style="margin:0 0 24px;font-size:15px;line-height:21px;">
                        {job_data['description']}
                      </p>
                        <a href="https://docs.google.com/forms/d/e/1FAIpQLSdcm0zkEqZjb1sDBaytj9ITLOX8L0LTD81HczJxRAS7LfvSKA/viewform?usp=header" target="_blank">
  <button style="padding: 10px 20px; font-size: 16px; border-radius: 8px; background-color: #4CAF50; color: white; border: none; cursor: pointer;">
    Fill Out the Form
  </button>
</a>

                      <p style="margin:0;font-size:14px;color:#777777;line-height:20px;">
                        This is an automated message from the Company Portal.
                      </p>
                    </td>
                  </tr>

                  <!-- Footer -->
                  <tr>
                    <td style="background-color:#f4f4f4;padding:15px;
                               text-align:center;font-size:12px;color:#999999;">
                      &copy; {year} Your Company Name. All rights reserved.
                    </td>
                  </tr>

                </table>
              </td>
            </tr>
          </table>
        </body>
        </html>
        """

        msg.attach(MIMEText(body, 'html'))

        # Connect to SMTP server and send
        with smtplib.SMTP(smtp_server, smtp_port) as server:
               server.ehlo()  # Identify ourselves to SMTP
               server.starttls()  # Upgrade to secure TLS
               server.ehlo()  # Re-identify after STARTTLS
               server.login(smtp_username, smtp_password)
               server.send_message(msg)

        print(f"Email sent successfully to {recipient_email}")
        return True
    except Exception as e:
        print(f"Error sending email: {e}")
        import traceback
        traceback.print_exc()
        return False


def get_unique_values(df, column):
    """Gets unique, sorted, non-empty values from a DataFrame column, handling comma-separated strings and lists."""
    values = set()
    if column in df.columns and not df[column].isnull().all():
        for item in df[column].dropna():
            item_str = str(item).strip()
            if item_str:
                # Handle actual lists (from DB array type) or comma-separated strings
                if isinstance(item, list):
                    # Ensure items in list are strings and clean them (remove proficiency)
                    values.update(str(x).split('(')[0].strip() for x in item if x and str(x).strip())
                elif ',' in item_str:
                     # Strip proficiency like " (Beginner)"
                    values.update(x.split('(')[0].strip() for x in item_str.split(',') if x.strip())
                else:
                     # Strip proficiency like " (Beginner)"
                    values.add(item_str.split('(')[0].strip())
    return sorted(list(values))


def filter_students(df, filters):
    """Filters the student DataFrame based on various criteria including job requirements."""
    if df.empty:
        print("Filter Students: Input DataFrame is empty.")
        return df

    filtered_df = df.copy()
    print(f"Filter Students: Starting with {len(filtered_df)} students. Filters: {filters}")

    try:
        # CGPA filter
        if filters.get('min_cgpa') is not None and filters['min_cgpa'] != '': # Check for not None explicitly
            try:
                min_cgpa_val = float(filters['min_cgpa'])
                if 'cgpa' in filtered_df.columns:
                    original_count = len(filtered_df)
                    filtered_df = filtered_df[filtered_df['cgpa'].fillna(0) >= min_cgpa_val]
                    print(f"  Applied CGPA >= {min_cgpa_val}: {original_count} -> {len(filtered_df)}")
            except (ValueError, TypeError):
                print(f"  Warning: Invalid CGPA filter value '{filters['min_cgpa']}'")

        # Backlogs filter
        if filters.get('max_backlogs') is not None and filters['max_backlogs'] != '': # Check for not None explicitly
            try:
                 max_backlogs_val = float(filters['max_backlogs'])
                 if 'backlogs' in filtered_df.columns:
                     original_count = len(filtered_df)
                     filtered_df = filtered_df[filtered_df['backlogs'] <= max_backlogs_val]
                     print(f"  Applied Backlogs <= {max_backlogs_val}: {original_count} -> {len(filtered_df)}")
            except (ValueError, TypeError):
                 print(f"  Warning: Invalid Backlogs filter value '{filters['max_backlogs']}'")

        # Department filter (added for NL Query possibility)
        if filters.get('department') and 'department' in filtered_df.columns:
            dept_filter = str(filters['department']).strip()
            if dept_filter:
                original_count = len(filtered_df)
                # Case-insensitive comparison
                filtered_df = filtered_df[filtered_df['department'].str.contains(dept_filter, case=False, na=False)]
                print(f"  Applied Department contains '{dept_filter}': {original_count} -> {len(filtered_df)}")


        # Helper for text contains filtering (case-insensitive) for comma-separated strings or lists
        def text_contains_any(series, patterns):
            if not patterns or series.isnull().all():
                return pd.Series([False] * len(series), index=series.index)

            patterns_set = {str(p).lower().strip() for p in patterns if p and str(p).strip()}
            if not patterns_set:
                return pd.Series([False] * len(series), index=series.index)

            def check_item(item):
                if pd.isna(item): return False
                item_elements = []
                current_item_elements = []
                if isinstance(item, list):
                    current_item_elements = [str(el).lower().split('(')[0].strip() for el in item if el]
                elif isinstance(item, str):
                    current_item_elements = [el.lower().split('(')[0].strip() for el in item.split(',') if el.strip()]
                else:
                    current_item_elements = [str(item).lower().split('(')[0].strip()]
                # Extend the main list
                item_elements.extend(el for el in current_item_elements if el) # Ensure no empty strings
                return any(elem in patterns_set for elem in item_elements)

            return series.apply(check_item)


        # Skills filter (checks programming_languages OR tools_technologies)
        if filters.get('skills'):
            skill_patterns = filters['skills']
            lang_col_exists = 'programming_languages' in filtered_df.columns
            tools_col_exists = 'tools_technologies' in filtered_df.columns
            lang_match = pd.Series([False] * len(filtered_df), index=filtered_df.index)
            tools_match = pd.Series([False] * len(filtered_df), index=filtered_df.index)

            if lang_col_exists: lang_match = text_contains_any(filtered_df['programming_languages'], skill_patterns)
            if tools_col_exists: tools_match = text_contains_any(filtered_df['tools_technologies'], skill_patterns)

            original_count = len(filtered_df)
            filtered_df = filtered_df[lang_match | tools_match]
            print(f"  Applied Skills filter ({skill_patterns}): {original_count} -> {len(filtered_df)}")


        # Preferred roles filter
        if filters.get('roles'):
            role_patterns = filters['roles']
            if 'preferred_roles' in filtered_df.columns:
                original_count = len(filtered_df)
                filtered_df = filtered_df[text_contains_any(filtered_df['preferred_roles'], role_patterns)]
                print(f"  Applied Roles filter ({role_patterns}): {original_count} -> {len(filtered_df)}")

        # Certifications filter (checks certifications OR detailed_certifications)
        if filters.get('certifications'):
            cert_patterns = filters['certifications']
            cert_match = pd.Series([False] * len(filtered_df), index=filtered_df.index)
            detail_cert_match = pd.Series([False] * len(filtered_df), index=filtered_df.index)

            if 'certifications' in filtered_df.columns: cert_match = text_contains_any(filtered_df['certifications'], cert_patterns)
            if 'detailed_certifications' in filtered_df.columns: detail_cert_match = text_contains_any(filtered_df['detailed_certifications'], cert_patterns)

            original_count = len(filtered_df)
            filtered_df = filtered_df[cert_match | detail_cert_match]
            print(f"  Applied Certifications filter ({cert_patterns}): {original_count} -> {len(filtered_df)}")


        # Boolean flags (check if column exists and is not null/empty string/empty list)
        def check_not_empty(series):
            if series is None or series.empty: return pd.Series([False] * len(filtered_df), index=filtered_df.index)
            # Handles NaN, None, empty strings, and potentially empty lists robustly
            return series.apply(lambda x: pd.notna(x) and ( (isinstance(x, str) and x.strip()!='') or (isinstance(x, list) and len(x)>0) or (not isinstance(x,(str,list))) ) )

        # Use the boolean value directly from filters dict (already processed in filter_students_route)
        if filters.get('has_hackathons') is True:
             if 'hackathons' in filtered_df.columns:
                original_count = len(filtered_df)
                filtered_df = filtered_df[check_not_empty(filtered_df['hackathons'])]
                print(f"  Applied Has Hackathons: {original_count} -> {len(filtered_df)}")
             else: print("  Skipped Has Hackathons: Column not found")

        if filters.get('has_experience') is True:
            proj_exists = 'projects' in filtered_df.columns
            intern_exists = 'internships' in filtered_df.columns
            has_proj = check_not_empty(filtered_df.get('projects')) if proj_exists else pd.Series([False] * len(filtered_df), index=filtered_df.index)
            has_intern = check_not_empty(filtered_df.get('internships')) if intern_exists else pd.Series([False] * len(filtered_df), index=filtered_df.index)
            original_count = len(filtered_df)
            filtered_df = filtered_df[has_proj | has_intern]
            print(f"  Applied Has Experience (Proj/Intern): {original_count} -> {len(filtered_df)}")


        if filters.get('has_research') is True:
            pub_exists = 'publications' in filtered_df.columns
            patent_exists = 'patents' in filtered_df.columns
            has_pub = check_not_empty(filtered_df.get('publications')) if pub_exists else pd.Series([False] * len(filtered_df), index=filtered_df.index)
            has_patent = check_not_empty(filtered_df.get('patents')) if patent_exists else pd.Series([False] * len(filtered_df), index=filtered_df.index)
            original_count = len(filtered_df)
            filtered_df = filtered_df[has_pub | has_patent]
            print(f"  Applied Has Research (Pub/Patent): {original_count} -> {len(filtered_df)}")


        # --- Job role-based filtering (Applied if job_id is present) ---
        job_id_filter = filters.get('job_id')
        if job_id_filter and job_id_filter != '' and supabase:
            try:
                job_id_val = int(job_id_filter)
                print(f"  Applying filters for Job ID: {job_id_val}")
                response = supabase.table('jobs').select('min_cgpa, required_skills, experience_level').eq('id', job_id_val).maybe_single().execute()

                if response.data:
                    job = response.data
                    print(f"    Job Requirements: {job}")
                    # Filter by minimum CGPA requirement
                    if job.get('min_cgpa') is not None and 'cgpa' in filtered_df.columns:
                        original_count = len(filtered_df)
                        filtered_df = filtered_df[filtered_df['cgpa'].fillna(0) >= float(job['min_cgpa'])]
                        print(f"    Applied Job CGPA >= {job['min_cgpa']}: {original_count} -> {len(filtered_df)}")

                    # Filter by required skills (ANY logic)
                    if job.get('required_skills'):
                        required_skills_list = job['required_skills']
                        lang_col_exists = 'programming_languages' in filtered_df.columns
                        tools_col_exists = 'tools_technologies' in filtered_df.columns
                        lang_match = pd.Series([False] * len(filtered_df), index=filtered_df.index)
                        tools_match = pd.Series([False] * len(filtered_df), index=filtered_df.index)

                        if lang_col_exists: lang_match = text_contains_any(filtered_df['programming_languages'], required_skills_list)
                        if tools_col_exists: tools_match = text_contains_any(filtered_df['tools_technologies'], required_skills_list)

                        original_count = len(filtered_df)
                        filtered_df = filtered_df[lang_match | tools_match]
                        print(f"    Applied Job Skills filter ({required_skills_list}): {original_count} -> {len(filtered_df)}")

                    # Filter by experience level (Checks if internships or projects exist if experience > 0)
                    if job.get('experience_level') and job['experience_level'] > 0:
                         intern_exists = 'internships' in filtered_df.columns
                         proj_exists = 'projects' in filtered_df.columns
                         has_intern = check_not_empty(filtered_df.get('internships')) if intern_exists else pd.Series([False] * len(filtered_df), index=filtered_df.index)
                         has_proj = check_not_empty(filtered_df.get('projects')) if proj_exists else pd.Series([False] * len(filtered_df), index=filtered_df.index)

                         if intern_exists or proj_exists:
                             original_count = len(filtered_df)
                             filtered_df = filtered_df[has_proj | has_intern]
                             print(f"    Applied Job Experience (>0 requires Internship/Project): {original_count} -> {len(filtered_df)}")
                         else: print("    Skipped Job Experience: Internships/Projects columns not found")
                else:
                    print(f"    Warning: Job ID {job_id_val} not found for filtering.")
                    flash(f"Warning: Job ID {job_id_val} not found, could not apply job-specific filters.", "warning")

            except ValueError:
                 print(f"  Invalid job_id format: {job_id_filter}")
            except Exception as e:
                print(f"  Error fetching/applying job details for filtering: {e}")
                flash(f"Warning: Could not apply filters for Job ID {job_id_filter}. Error: {e}", "warning")

    except Exception as e:
        print(f"Error during filtering process: {e}")
        import traceback
        traceback.print_exc()
        flash(f"An critical error occurred during filtering: {e}", "danger")
        return df # Return original df on major error

    print(f"Filter Students: Finished with {len(filtered_df)} students.")
    return filtered_df


def get_ai_insights(students_data, role):
    """Generates AI insights using Gemini for selected students and a role."""
    if not HAS_GEMINI:
        return "AI Insights disabled: Gemini API key not configured."
    if students_data.empty:
        return "No student data provided for analysis."

    print(f"Generating AI insights for {len(students_data)} students, role: {role}") # Debug

    # Format the student data for Gemini prompt (Simplified)
    students_prompt = f"Analyze the following {len(students_data)} students based *only* on the provided profile data for suitability regarding the **{role}** role:\n\n---\n"
    columns_to_include = [
        'full_name', 'department', 'cgpa', 'backlogs',
        'programming_languages', 'tools_technologies', 'soft_skills',
        'projects', 'internships', 'certifications', 'preferred_roles',
        'strengths', 'weaknesses'
    ]

    for index, student in students_data.iterrows():
        profile_str = f"**Student {index + 1}: {student.get('full_name', 'N/A')}**\n"
        for col in columns_to_include:
            if col != 'full_name':
                value = student.get(col)
                display_value = "N/A"
                if pd.notna(value):
                    if isinstance(value, list):
                        display_value = ", ".join(map(str, value)) if value else "N/A"
                    elif isinstance(value, float):
                        display_value = f"{value:.2f}"
                    elif isinstance(value, (int, np.integer)):
                        display_value = str(value)
                    elif isinstance(value, str) and value.strip():
                        display_value = value.strip()

                if display_value != "N/A":
                    profile_str += f"- {col.replace('_', ' ').title()}: {display_value}\n"
        students_prompt += profile_str + "---\n"

    students_prompt += f"""
    **Analysis Request for {role}:**

    1.  **Ranked List:** Provide a ranked list (Top 3-5 recommended) of these students for the role. Briefly justify each rank based *only* on the provided data points (e.g., relevant skills, experience, CGPA if applicable).
    2.  **Strengths & Potential Gaps:** For *each* student listed above, concisely identify 1-2 key strengths and 1-2 potential gaps or areas needing clarification *specifically* for the '{role}' role, based *only* on their profile.
    3.  **Overall Summary:** Briefly summarize which student(s) appear most promising overall for this *specific* role, considering the data provided. Mention any significant trade-offs.

    **Important Constraints:**
    *   Base your entire analysis *strictly* on the profile data given above.
    *   Do not invent or assume information not present.
    *   Focus on relevance to the specified '{role}'.
    *   Format the output clearly using Markdown (bolding, lists).
    """

    try:
        model = genai.GenerativeModel('gemini-1.5-flash') # Or gemini-pro
        # Safety settings can be adjusted if needed
        # safety_settings = [...]
        # response = model.generate_content(students_prompt, safety_settings=safety_settings)
        response = model.generate_content(students_prompt)

        if not response.parts:
             print("Warning: AI response might be blocked due to safety settings.")
             feedback = response.prompt_feedback if hasattr(response, 'prompt_feedback') else "No feedback available."
             print(f"Prompt Feedback: {feedback}")
             return f"AI analysis could not be completed (content potentially blocked by safety filters). Feedback: {feedback}"

        print("AI insights generated successfully.") # Debug
        return response.text
    except Exception as e:
        print(f"Error generating AI insights: {e}")
        import traceback
        traceback.print_exc()
        return f"Error generating AI insights: {str(e)}\n\nPlease check your Gemini API key and configuration."


# --- Chart Data Calculation Functions ---

def calculate_cgpa_distribution(df):
    """Calculates data for CGPA distribution chart."""
    result = {'labels': [], 'data': []}
    if df.empty or 'cgpa' not in df.columns or df['cgpa'].isnull().all():
        print("CGPA Calc: DataFrame empty or 'cgpa' column missing/all null.")
        return result
    try:
        bins = [0, 6, 7, 7.5, 8, 8.5, 9, 9.5, 10.1]
        labels = ['< 6.0', '6.0-6.9', '7.0-7.4', '7.5-7.9', '8.0-8.4', '8.5-8.9', '9.0-9.4', '9.5-10.0']
        cgpa_binned = pd.cut(df['cgpa'].dropna(), bins=bins, labels=labels, right=False, include_lowest=True)
        counts = cgpa_binned.value_counts().sort_index()
        result = {'labels': counts.index.tolist(), 'data': counts.values.tolist()}
        print(f"CGPA Calc Results: Labels={result['labels']}, Data={result['data']}") # DEBUG
        return result
    except Exception as e:
        print(f"Error calculating CGPA dist data: {e}")
        return result

def calculate_department_distribution(df):
    """Calculates data for department distribution chart."""
    result = {'labels': [], 'data': []}
    if df.empty or 'department' not in df.columns or df['department'].isnull().all():
        print("Dept Calc: DataFrame empty or 'department' column missing/all null.")
        return result
    try:
        counts = df['department'].value_counts()
        result = {'labels': counts.index.tolist(), 'data': counts.values.tolist()}
        print(f"Dept Calc Results: Labels={result['labels']}, Data={result['data']}") # DEBUG
        return result
    except Exception as e:
        print(f"Error calculating Dept dist data: {e}")
        return result

def calculate_skills_distribution(df, top_n=10):
    """Calculates data for top N programming skills distribution."""
    result = {'labels': [], 'data': []}
    if df.empty or 'programming_languages' not in df.columns:
        print("Skills Calc: DataFrame empty or 'programming_languages' column missing.")
        return result
    try:
        all_skills = []
        for skills_item in df['programming_languages'].dropna():
            skills = []
            if isinstance(skills_item, str) and skills_item.strip():
                skills = [s.split('(')[0].strip().lower() for s in skills_item.split(',') if s.strip()]
            elif isinstance(skills_item, list):
                skills = [str(s).split('(')[0].strip().lower() for s in skills_item if s and str(s).strip()]
            all_skills.extend(skills)

        if not all_skills: print("Skills Calc: No skills found after parsing."); return result
        skill_counts = pd.Series(all_skills).value_counts().nlargest(top_n)
        labels = [s.capitalize() for s in skill_counts.index.tolist()]
        result = {'labels': labels, 'data': skill_counts.values.tolist()}
        print(f"Skills Calc Results: Labels={result['labels']}, Data={result['data']}") # DEBUG
        return result
    except Exception as e:
        print(f"Error calculating Skills dist data: {e}")
        return result

def calculate_roles_distribution(df, top_n=10):
    """Calculates data for top N preferred roles distribution."""
    result = {'labels': [], 'data': []}
    if df.empty or 'preferred_roles' not in df.columns:
        print("Roles Calc: DataFrame empty or 'preferred_roles' column missing.")
        return result
    try:
        all_roles = []
        for roles_item in df['preferred_roles'].dropna():
            roles = []
            if isinstance(roles_item, str) and roles_item.strip():
                roles = [r.strip().lower() for r in roles_item.split(',') if r.strip()]
            elif isinstance(roles_item, list):
                roles = [str(r).strip().lower() for r in roles_item if r and str(r).strip()]
            all_roles.extend(roles)

        if not all_roles: print("Roles Calc: No roles found after parsing."); return result
        role_counts = pd.Series(all_roles).value_counts().nlargest(top_n)
        labels = [r.title() for r in role_counts.index.tolist()]
        result = {'labels': labels, 'data': role_counts.values.tolist()}
        print(f"Roles Calc Results: Labels={result['labels']}, Data={result['data']}") # DEBUG
        return result
    except Exception as e:
        print(f"Error calculating Roles dist data: {e}")
        return result

# --- Routes ---

@app.route('/')
def home():
    """Redirects to dashboard if logged in, otherwise to login page."""
    if 'company_email' in session:
        return redirect(url_for('dashboard'))
    return redirect(url_for('login'))


@app.route('/login', methods=['GET', 'POST'])
def login():
    if not supabase:
        return make_response(render_template('company_login.html', error="Database service unavailable. Please try again later."), 503)

    error = None
    if request.method == 'POST':
        email = request.form.get('email')
        password = request.form.get('password')

        if not email or not password:
             error = 'Email and password are required.'
             return render_template('company_login.html', error=error)

        try:
            print(f"--- Attempting login for: {email}")
            response = supabase.table('companies').select('*').eq('email', email).maybe_single().execute()
            print(f"--- Supabase response status: {response.data is not None}")

            if response.data:
                company_data = response.data
                print(f"--- Company data found: {company_data.keys()}")
                stored_hash = company_data.get('password')
                print(f"--- Stored hash found: {stored_hash is not None}")

                if stored_hash and verify_password(stored_hash, password):
                    print("--- Password verified")
                    required_keys = ['id', 'email', 'company_name']
                    if all(k in company_data for k in required_keys):
                        session['company_id'] = company_data['id']
                        session['company_email'] = company_data['email']
                        session['company_name'] = company_data['company_name']
                        session.permanent = True
                        print("--- Session set successfully")
                        flash(f"Welcome back, {session['company_name']}!", "success")
                        return redirect(url_for('dashboard'))
                    else:
                        print(f"--- Error: Missing required keys in company_data. Found: {company_data.keys()}")
                        error = "Login failed: Incomplete company profile data in database."
                else:
                    print("--- Password verification failed")
                    error = 'Invalid credentials. Please try again.'
            else:
                print(f"--- Company email '{email}' not found in database.")
                error = 'Company email not found.'
        except Exception as e:
            print(f"--- Login error exception: {e}")
            import traceback
            traceback.print_exc()
            error = 'An error occurred during login. Please contact support.'

    if 'company_email' in session:
        return redirect(url_for('dashboard'))
    return render_template('company_login.html', error=error)


@app.route('/logout')
def logout():
    """Logs the company out."""
    session.pop('company_id', None)
    session.pop('company_email', None)
    session.pop('company_name', None)
    session.pop('filter_options_cache', None) # Clear cached options on logout
    flash("You have been logged out.", "info")
    return redirect(url_for('login'))


@app.route('/dashboard')
def dashboard():
    """Displays the main company dashboard."""
    if 'company_email' not in session:
        flash("Please log in to access the dashboard.", "warning")
        return redirect(url_for('login'))
    if not supabase:
        flash("Database service unavailable.", "danger")
        return render_template('company_dashboard.html', company_name=session.get('company_name', 'Company'), students=[], filter_options={}, initial_chart_data='{}', jobs=[], error="Database connection failed")

    # Load student data
    df = load_student_data()

    # Prepare data for filters
    filter_options = {
        'departments': get_unique_values(df, 'department'),
        'programming_languages': get_unique_values(df, 'programming_languages'),
        'tools': get_unique_values(df, 'tools_technologies'),
        'roles': get_unique_values(df, 'preferred_roles'),
        'certifications': sorted(list(set(get_unique_values(df, 'certifications') + get_unique_values(df, 'detailed_certifications'))))
    }
    # --- Cache filter options in session ---
    session['filter_options_cache'] = filter_options
    # --- End Caching ---

    # --- Calculate Initial Chart Data ---
    initial_chart_data = {
        'cgpa': calculate_cgpa_distribution(df),
        'department': calculate_department_distribution(df),
        'skills': calculate_skills_distribution(df),
        'roles': calculate_roles_distribution(df)
    }
    print("\n--- Initial Chart Data (Backend - Dashboard Route) ---")
    try:
        print(json.dumps(initial_chart_data, indent=2))
    except Exception as json_e: print(f"Error dumping initial chart data: {json_e}")
    print("-----------------------------------------------------\n")

    # Fetch jobs posted by this company
    jobs_list = []
    try:
        response = supabase.table('jobs').select('id, title').eq('company_id', session['company_id']).order('posted_date', desc=True).execute()
        jobs_list = response.data if response.data else []
    except Exception as e:
        print(f"Error fetching jobs for dashboard: {e}")
        flash("Could not load job postings.", "warning")

    # Convert DataFrame NaNs to None for JSON serialization
    display_cols = ['full_name', 'email', 'department', 'cgpa', 'backlogs', 'programming_languages', 'preferred_roles', 'internships', 'projects', 'hackathons', 'publications']
    # Only select columns that actually exist in the dataframe
    existing_display_cols = [col for col in display_cols if col in df.columns]
    students_list = df[existing_display_cols].replace({np.nan: None}).to_dict('records') if not df.empty else []

    return render_template(
        'company_dashboard.html',
        initial_chart_data=initial_chart_data, # Pass dict directly
        company_name=session['company_name'],
        students=students_list,
        filter_options=filter_options, # Pass for initial rendering
        jobs=jobs_list
    )


@app.route('/filter_students', methods=['POST'])
def filter_students_route():
    """Handles AJAX request to filter students and provides updated chart data."""
    if 'company_email' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    if not supabase:
         return jsonify({'error': 'Database service unavailable'}), 503

    print("\n--- Filter Students Route Called ---") # Debug
    df = load_student_data()
    if df.empty:
         print("Filter Students Route: No student data loaded.")
         return jsonify({'students': [], 'chart_data': {}, 'count': 0})

    filters = request.form.to_dict()

    # Explicitly process boolean flags from form data ('on' means True)
    filters['has_hackathons'] = filters.get('has_hackathons') == 'on'
    filters['has_experience'] = filters.get('has_experience') == 'on'
    filters['has_research'] = filters.get('has_research') == 'on'

    # Get lists correctly
    filters['skills'] = request.form.getlist('skills')
    filters['roles'] = request.form.getlist('roles')
    filters['certifications'] = request.form.getlist('certifications')

    # Apply filters
    filtered_df = filter_students(df, filters)

    # --- Calculate Chart Data for Filtered Results ---
    filtered_chart_data = {
        'cgpa': calculate_cgpa_distribution(filtered_df),
        'department': calculate_department_distribution(filtered_df),
        'skills': calculate_skills_distribution(filtered_df),
        'roles': calculate_roles_distribution(filtered_df)
    }
    print("\n--- Filtered Chart Data (Backend - Filter Route) ---")
    try: print(json.dumps(filtered_chart_data, indent=2))
    except Exception as json_e: print(f"Error dumping filtered chart data: {json_e}")
    print("----------------------------------------------------\n")

    # Prepare data for JSON response
    display_cols = ['full_name', 'email', 'department', 'cgpa', 'backlogs', 'programming_languages', 'preferred_roles', 'internships', 'projects', 'hackathons', 'publications']
    existing_display_cols = [col for col in display_cols if col in filtered_df.columns]
    students_result = filtered_df[existing_display_cols].replace({np.nan: None}).to_dict('records') if not filtered_df.empty else []

    print(f"Filter Students Route: Returning {len(students_result)} students.") # Debug
    return jsonify({
        'students': students_result,
        'chart_data': filtered_chart_data,
        'count': len(filtered_df)
    })


@app.route('/process_nl_query', methods=['POST'])
def process_nl_query():
    """Handles AJAX request to convert natural language query to filters."""
    if 'company_email' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    if not supabase:
        return jsonify({'error': 'Database service unavailable'}), 503
    if not HAS_GEMINI:
        return jsonify({'error': 'AI Query service not configured or unavailable.'}), 503

    nl_query = request.json.get('query')
    if not nl_query:
        return jsonify({'error': 'No query provided.'}), 400

    print(f"Processing NL Query: '{nl_query}'")

    # Retrieve cached filter options
    cached_options = session.get('filter_options_cache')
    if not cached_options:
        # Fallback: Regenerate if not in session (should be rare)
        print("Warning: Filter options cache miss in NL Query route, regenerating.")
        df_temp = load_student_data()
        cached_options = {
            'departments': get_unique_values(df_temp, 'department'),
            'programming_languages': get_unique_values(df_temp, 'programming_languages'),
            'tools': get_unique_values(df_temp, 'tools_technologies'),
            'roles': get_unique_values(df_temp, 'preferred_roles'),
            'certifications': sorted(list(set(get_unique_values(df_temp, 'certifications') + get_unique_values(df_temp, 'detailed_certifications'))))
        }
        session['filter_options_cache'] = cached_options # Cache it now

    # Combine skills for context
    all_skill_examples = list(set(cached_options.get('programming_languages', []) + cached_options.get('tools', [])))

    # --- Construct Prompt for Gemini ---
    prompt = f"""
    Analyze the following natural language query from a recruiter and convert it into a JSON object containing filter parameters for a student database.

    **Available Filter Fields and Keys (Use these keys in the JSON output):**
    *   `min_cgpa`: Minimum CGPA (float, 0.0 to 10.0).
    *   `max_backlogs`: Maximum number of backlogs allowed (integer, >= 0).
    *   `skills`: List of required skills (strings). Match these against programming languages OR tools.
    *   `roles`: List of preferred job roles (strings).
    *   `certifications`: List of certifications (strings).
    *   `department`: Specific department name (string). Extract if mentioned clearly.
    *   `has_hackathons`: Boolean (true if user mentions hackathons or competitions).
    *   `has_experience`: Boolean (true if user mentions projects or internships).
    *   `has_research`: Boolean (true if user mentions research, publications, or patents).

    **Context - Available Values (Examples - Use for guidance, not exhaustive):**
    *   Departments: {json.dumps(cached_options.get('departments', [])[:10])}...
    *   Skills (Programming/Tools): {json.dumps(all_skill_examples[:15])}...
    *   Roles: {json.dumps(cached_options.get('roles', [])[:10])}...
    *   Certifications: {json.dumps(cached_options.get('certifications', [])[:10])}...

    **Recruiter's Query:**
    "{nl_query}"

    **Instructions:**
    1.  Parse the query to extract filtering criteria.
    2.  Map criteria to the correct filter keys listed above.
    3.  For list fields (`skills`, `roles`, `certifications`), output a JSON list of strings. Include the user's terms.
    4.  For boolean fields (`has_...`), output `true` only if explicitly mentioned. Otherwise, omit the key or set to `false`.
    5.  For `min_cgpa` and `max_backlogs`, extract the numeric value.
    6.  For `department`, extract the specific department name mentioned.
    7.  If a criterion isn't mentioned, omit its key from the JSON.
    8.  Output *only* the JSON object, without any explanations or markdown formatting.

    **Example Input Query:** "Find computer science students with CGPA over 7.5, no backlogs, and who know Python or Java. Experience with projects is a plus."
    **Example JSON Output:**
    {{
      "department": "Computer Science",
      "min_cgpa": 7.5,
      "max_backlogs": 0,
      "skills": ["Python", "Java"],
      "has_experience": true
    }}

    **Now, process the recruiter's query above and generate the JSON output.**
    """

    try:
        print("--- Sending NL prompt to Gemini ---")
        model = genai.GenerativeModel('gemini-1.5-flash') # Or gemini-pro
        response = model.generate_content(prompt)

        # Clean the response (remove markdown backticks, etc.)
        print(f"--- Gemini Raw Response Text ---\n{response.text}\n-----------------------------")
        cleaned_response_text = response.text.strip()
        if cleaned_response_text.startswith('```json'): cleaned_response_text = cleaned_response_text[7:]
        if cleaned_response_text.startswith('```'): cleaned_response_text = cleaned_response_text[3:]
        if cleaned_response_text.endswith('```'): cleaned_response_text = cleaned_response_text[:-3]
        cleaned_response_text = cleaned_response_text.strip()

        if not cleaned_response_text:
             raise ValueError("Gemini returned an empty response after cleaning.")

        filter_params = json.loads(cleaned_response_text)
        print(f"--- Parsed Filter Params ---\n{json.dumps(filter_params, indent=2)}\n--------------------------")

        if not isinstance(filter_params, dict):
            raise ValueError("Gemini response was not a valid JSON object.")

        # Optional: Further validation/type conversion can be added here if needed

        return jsonify({'filters': filter_params})

    except json.JSONDecodeError as e:
        print(f"Error decoding Gemini JSON response: {e}")
        print(f"Response text was: {cleaned_response_text}") # Log cleaned text
        return jsonify({'error': f'AI could not generate valid filter parameters. Please rephrase your query. (JSON Error: {e}). Raw response: {response.text}'}), 400
    except Exception as e:
        print(f"Error processing NL query with Gemini: {e}")
        import traceback
        traceback.print_exc()
        return jsonify({'error': f'An error occurred while processing the AI query: {e}'}), 500


@app.route('/ai_insights', methods=['POST'])
def ai_insights_route():
    """Handles AJAX request for AI insights."""
    if 'company_email' not in session:
        return jsonify({'error': 'Not authenticated'}), 401
    if not supabase:
         return jsonify({'error': 'Database service unavailable'}), 503
    if not HAS_GEMINI:
        return jsonify({'error': 'AI Insights service not configured or unavailable.'}), 503

    df = load_student_data()
    selected_emails = request.form.getlist('selected_students')
    role = request.form.get('role', 'General Role Analysis')

    print(f"AI Insights requested for emails: {selected_emails} and role: {role}")

    if not selected_emails:
        print("No students selected for AI analysis.")
        return jsonify({'error': 'No students selected for analysis.'}), 400

    selected_students_df = df[df['email'].isin(selected_emails)].copy()

    if selected_students_df.empty:
        print(f"Selected student emails not found in loaded data: {selected_emails}")
        return jsonify({'error': 'Selected student data could not be found.'}), 404

    insights = get_ai_insights(selected_students_df, role)
    return jsonify({'insights': insights})


@app.route('/export_filtered', methods=['POST'])
def export_filtered():
    """Exports the currently filtered student list (sent from client) to CSV."""
    if 'company_email' not in session:
        return jsonify({"error": "Authentication required for export"}), 401

    student_data_json = request.form.get('filtered_students')
    if not student_data_json:
        return jsonify({"error": "No student data provided"}), 400

    try:
        student_data = json.loads(student_data_json)
        if not student_data:
             return jsonify({"error": "No students to export"}), 400

        df_to_export = pd.DataFrame(student_data)
        export_columns = [
            'full_name', 'email', 'department', 'cgpa', 'backlogs',
            'programming_languages', 'tools_technologies', 'soft_skills',
            'projects', 'internships', 'certifications', 'detailed_certifications',
            'hackathons', 'publications', 'patents',
            'preferred_roles', 'preferred_location', 'strengths', 'weaknesses',
            'typing_speed', 'linkedin_profile', 'github_profile', 'portfolio_url'
        ]
        available_export_cols = [col for col in export_columns if col in df_to_export.columns]
        df_to_export = df_to_export[available_export_cols]

    except json.JSONDecodeError:
        print("Error decoding JSON for export")
        return jsonify({"error": "Invalid data format for export"}), 400
    except Exception as e:
        print(f"Error preparing export data: {e}")
        return jsonify({"error": f"Server error during export preparation: {e}"}), 500

    try:
        mem_file = io.BytesIO()
        mem_file.write(u'\ufeff'.encode('utf8')) # BOM for Excel
        df_to_export.to_csv(mem_file, index=False, encoding='utf-8-sig')
        mem_file.seek(0)

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        response = make_response(send_file(
            mem_file,
            mimetype='text/csv',
            download_name=f'filtered_students_{timestamp}.csv',
            as_attachment=True
        ))
        response.headers['Content-Disposition'] = f"attachment; filename=filtered_students_{timestamp}.csv"
        response.headers["Content-type"] = "text/csv; charset=utf-8"
        print(f"Exporting {len(df_to_export)} students to CSV.")
        return response

    except Exception as e:
        print(f"Error sending export file: {e}")
        return jsonify({"error": f"Server error during file generation: {e}"}), 500


@app.route('/jobs', methods=['GET', 'POST'])
def jobs():
    """Displays job postings and handles new job creation."""
    if 'company_email' not in session:
        flash("Please log in to manage job postings.", "warning")
        return redirect(url_for('login'))
    if not supabase:
        flash("Database service unavailable.", "danger")
        return render_template('jobs.html', jobs=[], skills=[], company_name=session.get('company_name', 'Company'),
                               error="Database connection failed")

    company_id = session.get('company_id')
    if not company_id:
        flash("Company session error. Please log in again.", "danger")
        return redirect(url_for('logout'))

    # Load student data once for skills list generation
    df_students = load_student_data()
    prog_langs = get_unique_values(df_students, 'programming_languages')
    tools_tech = get_unique_values(df_students, 'tools_technologies')
    available_skills = sorted(list(set(prog_langs + tools_tech)))

    if request.method == 'POST':
        try:
            required_skills_list = request.form.getlist('required_skills')
            min_cgpa_str = request.form.get('min_cgpa')
            experience_str = request.form.get('experience_level', '0')
            title = request.form.get('title')
            description = request.form.get('description')
            location = request.form.get('location')

            # Basic validation
            error_msg = None
            if not title:
                error_msg = "Job Title is required."
            elif not description:
                error_msg = "Job Description is required."
            elif not required_skills_list:
                error_msg = "At least one required skill must be selected."
            elif not location:
                error_msg = "Location is required."

            if error_msg:
                flash(error_msg, "warning")
                response = supabase.table('jobs').select('*').eq('company_id', company_id).order('posted_date',
                                                                                                 desc=True).execute()
                jobs_list = response.data if response.data else []
                return render_template('jobs.html', jobs=jobs_list, skills=available_skills,
                                       company_name=session['company_name'])

            new_job_data = {
                'company_id': company_id,
                'title': title,
                'description': description,
                'required_skills': required_skills_list,  # Store as array
                'min_cgpa': float(min_cgpa_str) if min_cgpa_str else None,
                'experience_level': int(experience_str) if experience_str.isdigit() else 0,
                'location': location
            }
            response = supabase.table('jobs').insert(new_job_data).execute()

            if response.data:
                flash("New job posted successfully!", "success")

                # Send email notification about the new job
                email_sent = send_job_email(new_job_data)
                if email_sent:
                    flash("Job details sent by email successfully.", "info")
                else:
                    flash("Job posted but email notification failed to send.", "warning")
            else:
                print(f"Job insert response structure: {response}")
                flash("Failed to post job. Please check details and try again.", "danger")

        except Exception as e:
            print(f"Error posting job: {e}")
            import traceback;
            traceback.print_exc()
            flash(f"An error occurred while posting the job: {e}", "danger")

        return redirect(url_for('jobs'))  # Redirect after POST

    # GET request handling
    jobs_list = []
    try:
        response_jobs = supabase.table('jobs').select('*').eq('company_id', company_id).order('posted_date',
                                                                                              desc=True).execute()
        jobs_list = response_jobs.data if response_jobs.data else []
    except Exception as e:
        print(f"Error fetching data for jobs page: {e}")
        flash("Could not load job data.", "warning")

    return render_template('jobs.html', jobs=jobs_list, skills=available_skills, company_name=session['company_name'])


@app.route('/job/<int:job_id>')
def job_details(job_id):
    """Displays details for a specific job and eligible candidates."""
    if 'company_email' not in session:
        flash("Please log in to view job details.", "warning")
        return redirect(url_for('login'))
    if not supabase:
        flash("Database service unavailable.", "danger")
        return render_template('job_details.html', job=None, candidates_full=[], candidates_table=[], company_name=session.get('company_name', 'Company'), error="Database connection failed")

    job_data = None
    eligible_candidates_list = []
    candidates_full_list = []
    company_id = session.get('company_id')

    try:
        # Fetch the specific job details
        response_job = supabase.table('jobs').select('*').eq('id', job_id).maybe_single().execute()
        job_data = response_job.data

        if not job_data:
            flash("Job not found.", "danger")
            return redirect(url_for('jobs'))

        if job_data.get('company_id') != company_id:
           flash("You are not authorized to view details for this job.", "danger")
           print(f"Unauthorized access attempt to job {job_id} by company {company_id}")
           return redirect(url_for('jobs'))

        # Find eligible candidates
        df_students = load_student_data()
        if not df_students.empty:
            filters = {'job_id': job_id}
            eligible_df = filter_students(df_students.copy(), filters)

            # Prepare list for the table view (limited columns)
            display_cols_table = ['full_name', 'email', 'department', 'cgpa', 'programming_languages', 'projects', 'internships']
            existing_display_cols_table = [col for col in display_cols_table if col in eligible_df.columns]
            eligible_candidates_list = eligible_df[existing_display_cols_table].replace({np.nan: None}).to_dict('records') if not eligible_df.empty else []

            # Prepare list with full data for the modal
            candidates_full_list = eligible_df.replace({np.nan: None}).to_dict('records') if not eligible_df.empty else []

        else:
            flash("Could not load student data to find eligible candidates.", "warning")

    except Exception as e:
        print(f"Error fetching job details or candidates for job {job_id}: {e}")
        import traceback; traceback.print_exc()
        flash("An error occurred while loading job details or finding candidates.", "danger")
        if not job_data: return redirect(url_for('jobs'))

    return render_template(
        'job_details.html',
        job=job_data,
        candidates_full=json.dumps(candidates_full_list), # Pass full data as JSON string for JS
        candidates_table=eligible_candidates_list, # Data for the HTML table
        company_name=session.get('company_name', 'Company')
    )


# --- Error Handlers (Use base.html now) ---
@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_server_error(e):
    print(f"Server Error: {e}")
    import traceback
    traceback.print_exc()
    return render_template('500.html', error=e), 500

# --- Context Processor for Year ---
@app.context_processor
def inject_now():
    return {'now': datetime.utcnow()}

# --- Run Application ---
if __name__ == '__main__':
    if not supabase:
         print("\nWARNING: Supabase client failed to initialize.")
         print("Application will run but database operations will likely fail.")
         print("Check your .env file and Supabase project status.\n")
    port = int(os.environ.get("PORT", 5001))
    debug_mode = os.environ.get("FLASK_DEBUG", "true").lower() == "true"
    print(f"Starting Flask app on port {port} with debug mode: {debug_mode}")
    app.run(debug=debug_mode, port=port, host='0.0.0.0')