File size: 43,607 Bytes
3a32bd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import streamlit as st
import os
import json
import tempfile
from pathlib import Path
from PIL import Image
import time
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Try to import components with fallbacks
try:
    from ocr_client import OCRClient
    from verifier import CertificateVerifier
    OCR_AVAILABLE = True
except ImportError as e:
    logger.warning(f"OCR components not available: {e}")
    OCR_AVAILABLE = False
    OCRClient = None
    CertificateVerifier = None

# Try to import seal detection with priority: YOLOv8 > DETR > OpenCV > Fallback
SEAL_DETECTION_AVAILABLE = False
SealDetector = None

try:
    # First try YOLOv8-based detector (most accurate - 99%)
    from yolo_seal_detector import YOLOSealDetector as SealDetector
    SEAL_DETECTION_AVAILABLE = True
    SEAL_METHOD = "YOLOv8"
    logger.info("Using YOLOv8 (99% accurate) seal detector")
except ImportError:
    try:
        # Try DETR-based detector
        from detr_seal_detector import DETRSealDetector as SealDetector
        SEAL_DETECTION_AVAILABLE = True
        SEAL_METHOD = "DETR"
        logger.info("Using DETR (transformer-based) seal detector")
    except ImportError:
        try:
            # Fallback to OpenCV detector
            from seal_detector import SealDetector
            SEAL_DETECTION_AVAILABLE = True
            SEAL_METHOD = "OpenCV"
            logger.info("Using OpenCV seal detector (legacy)")
        except ImportError:
            try:
                # Final fallback
                from seal_detector_fallback import SealDetectorFallback as SealDetector  
                SEAL_DETECTION_AVAILABLE = True
                SEAL_METHOD = "Fallback"
                logger.warning("Using fallback seal detector")
            except ImportError:
                logger.warning("No seal detection available")

# Try to import ViT classifier with fallback (ONNX first, then PyTorch)
VIT_AVAILABLE = False
ViTSealClassifier = None

try:
    # Try ONNX version first (faster)
    from vit_seal_classifier_onnx import ViTSealClassifierONNX as ViTSealClassifier
    VIT_AVAILABLE = True
    logger.info("ViT classifier available (ONNX)")
except ImportError:
    try:
        # Fallback to PyTorch version
        from vit_seal_classifier import ViTSealClassifier
        VIT_AVAILABLE = True
        logger.info("ViT classifier available (PyTorch)")
    except ImportError:
        logger.warning("ViT classifier not available - using demo mode")
        VIT_AVAILABLE = False

# Page configuration
st.set_page_config(
    page_title="Certificate Verification System",
    page_icon="๐ŸŽ“",
    layout="wide"
)

def init_session_state():
    """Initialize session state variables."""
    if 'verification_result' not in st.session_state:
        st.session_state.verification_result = None
    if 'ocr_result' not in st.session_state:
        st.session_state.ocr_result = None
    if 'seal_result' not in st.session_state:
        st.session_state.seal_result = None
    if 'cropped_seals' not in st.session_state:
        st.session_state.cropped_seals = None
    if 'uploaded_file' not in st.session_state:
        st.session_state.uploaded_file = None

def display_verification_result(result, seal_result=None):
    """Display the verification result in a structured format."""
    
    # Final Decision Card
    st.subheader("๐ŸŽฏ Final Verification Decision")
    
    # Determine final decision based on OCR and Seal results
    ocr_status = "Pass" if result['decision'] == 'AUTHENTIC' else "Fail"
    seal_status = seal_result.get('status', 'Unknown') if seal_result else 'Unknown'
    
    # Improved final decision logic: Security-first approach
    # CRITICAL: If seals are detected as fake, the certificate MUST be rejected
    ocr_confidence = result.get('final_score', 0)
    seal_confidence = seal_result.get('confidence', 0) if seal_result else 0
    
    # Security-first decision criteria:
    both_pass = (ocr_status == "Pass" and seal_status == "Pass")
    
    # CRITICAL SECURITY CHECK: If fake seals detected with high confidence, REJECT
    fake_seals_detected = False
    if seal_result and seal_result.get('details'):
        fake_count = seal_result['details'].get('fake_seals', 0)
        total_seals = seal_result['details'].get('total_seals', 0)
        if fake_count > 0 and seal_confidence > 0.7:  # High confidence fake detection
            fake_seals_detected = True
    
    # REJECT if fake seals detected with high confidence
    if fake_seals_detected:
        final_decision = "Fake"
        rejection_reason = f"High confidence fake seal detection ({seal_confidence:.1%})"
    else:
        # Only pass if both OCR and seals pass, or if no seal verification was performed
        if seal_result is None:  # No seal verification
            final_decision = "Real" if (ocr_status == "Pass" and ocr_confidence > 0.8) else "Fake"
        else:  # Seal verification was performed
            final_decision = "Real" if both_pass else "Fake"
    
    # Display final decision with color coding and reason
    if final_decision == "Real":
        st.success("๐ŸŽ‰ **CERTIFICATE VERIFIED AS AUTHENTIC** โœ…")
    else:
        if 'rejection_reason' in locals():
            st.error(f"โŒ **CERTIFICATE VERIFICATION FAILED** โŒ\n\n**Reason**: {rejection_reason}")
        else:
            st.error("โŒ **CERTIFICATE VERIFICATION FAILED** โŒ")
    
    # Create columns for results
    col1, col2, col3 = st.columns(3)
    
    with col1:
        st.metric("Final Decision", final_decision)
    
    with col2:
        combined_confidence = (result['final_score'] + seal_result.get('confidence', 0.5)) / 2 if seal_result else result['final_score']
        st.metric("Overall Confidence", f"{combined_confidence:.2%}")
    
    with col3:
        reg_no = result['registration_no'] or 'Not Found'
        st.info(f"**Registration:** {reg_no}")
    
    # Step-by-step results
    st.markdown("---")
    st.subheader("๐Ÿ“‹ Verification Steps")
    
    # Step 1: OCR Verification
    with st.container():
        st.markdown("### Step 1: OCR Text Verification")
        col1, col2 = st.columns([1, 3])
        
        with col1:
            if ocr_status == "Pass":
                st.success("โœ… PASS")
            else:
                st.error("โŒ FAIL")
        
        with col2:
            decision = result['decision']
            if decision == 'AUTHENTIC':
                st.write("โœ… Certificate text matches database records")
            elif decision == 'SUSPECT':
                st.write("โš ๏ธ Certificate text has discrepancies - requires review")
            else:
                st.write("โŒ Certificate text does not match database records")
            
            st.metric("OCR Confidence", f"{result['final_score']:.2%}")
    
    # Step 2: Seal Verification
    with st.container():
        st.markdown("### Step 2: Seal/Stamp Verification")
        col1, col2 = st.columns([1, 3])
        
        with col1:
            if seal_result:
                if seal_result.get('status') == 'Pass':
                    st.success("โœ… PASS")
                else:
                    st.error("โŒ FAIL")
            else:
                st.warning("โš ๏ธ NOT CHECKED")
        
        with col2:
            if seal_result:
                reason = seal_result.get('reason', 'No reason provided')
                st.write(reason)
                if 'confidence' in seal_result:
                    st.metric("Seal Confidence", f"{seal_result['confidence']:.2%}")
                
                # Show individual seal results if available
                if 'individual_predictions' in seal_result:
                    with st.expander(f"๐Ÿ“ธ Individual Seal Results ({len(seal_result['individual_predictions'])} seals found)"):
                        for i, pred in enumerate(seal_result['individual_predictions']):
                            seal_status = pred.get('seal_status', 'Unknown')
                            confidence = pred.get('confidence', 0)
                            
                            if seal_status == 'Real':
                                st.write(f"**Seal {i+1}:** โœ… {seal_status} ({confidence:.1%} confidence)")
                                st.success("๐Ÿ›๏ธ Visvesvaraya Technological University")
                            elif seal_status == 'Fake':
                                st.write(f"**Seal {i+1}:** โŒ {seal_status} ({confidence:.1%} confidence)")
                            else:
                                st.write(f"**Seal {i+1}:** {seal_status} ({confidence:.1%} confidence)")
            else:
                st.write("โš ๏ธ Seal verification not performed")
                st.info("Enable seal verification in the sidebar to check seal authenticity")
    
    # Detailed results in expandable sections
    st.markdown("---")
    
    with st.expander("๐Ÿ“‹ Detailed OCR Verification Results", expanded=False):
        # Database record vs OCR extracted
        col1, col2 = st.columns(2)
        
        with col1:
            st.write("**Database Record:**")
            if result['db_record']:
                db_record = result['db_record']
                st.json({
                    'Name': db_record['name'],
                    'Institution': db_record['institution'], 
                    'Degree': db_record['degree'],
                    'Year': db_record['year'],
                    'Reg No': db_record['reg_no']
                })
            else:
                st.write("No matching record found")
        
        with col2:
            st.write("**OCR Extracted:**")
            ocr_data = result['ocr_extracted']
            st.json({
                'Name': ocr_data.get('name', 'Not extracted'),
                'Institution': ocr_data.get('institution', 'Not extracted'),
                'Degree': ocr_data.get('degree', 'Not extracted'),
                'Year': ocr_data.get('year', 'Not extracted')
            })
        
        # Field scores
        if result['field_scores']:
            st.subheader("๐ŸŽฏ Field Comparison Scores")
            for field, score in result['field_scores'].items():
                st.progress(score, text=f"{field.title()}: {score:.1%}")
        
        # Reasons
        st.subheader("๐Ÿ’ก Analysis Reasons")
        for reason in result['reasons']:
            st.write(f"โ€ข {reason}")
        
        # Subject Grades Verification (NEW)
        if result.get('registration_no'):
            reg_no = result['registration_no']
            verifier = st.session_state.get('verifier')
            if verifier:
                try:
                    # Lookup subjects from database
                    subjects = verifier._lookup_subjects(reg_no)
                    
                    if subjects:
                        st.subheader("๐Ÿ“š Subject Grades Verification")
                        
                        # Show subjects in a table
                        import pandas as pd
                        df = pd.DataFrame(subjects)
                        
                        # Display summary with comparison
                        import sqlite3
                        import re
                        conn = sqlite3.connect('certs.db')
                        cursor = conn.cursor()
                        cursor.execute("""
                            SELECT total_credits_earned, sgpa, cgpa
                            FROM certificate_summary
                            WHERE reg_no = ?
                        """, (reg_no,))
                        summary = cursor.fetchone()
                        conn.close()
                        
                        # Extract SGPA/CGPA from OCR text
                        ocr_text = ocr_data.get('raw_text', '')
                        
                        # Try pattern: "SGPA CGPA 9.95 9.78" (both on same line)
                        combined_match = re.search(r'SGPA\s+CGPA\s+([0-9.]+)\s+([0-9.]+)', ocr_text, re.IGNORECASE)
                        
                        if combined_match:
                            # Both values on same line: first is SGPA, second is CGPA
                            ocr_sgpa = float(combined_match.group(1))
                            ocr_cgpa = float(combined_match.group(2))
                        else:
                            # Try separate patterns (values on different lines)
                            sgpa_match = re.search(r'SGPA[:\s]+([0-9.]+)', ocr_text, re.IGNORECASE)
                            cgpa_match = re.search(r'CGPA[:\s]+([0-9.]+)', ocr_text, re.IGNORECASE)
                            
                            ocr_sgpa = float(sgpa_match.group(1)) if sgpa_match else None
                            ocr_cgpa = float(cgpa_match.group(1)) if cgpa_match else None
                        
                        if summary:
                            db_credits, db_sgpa, db_cgpa = summary
                            
                            col1, col2, col3 = st.columns(3)
                            
                            with col1:
                                st.metric("Total Credits (DB)", db_credits)
                            
                            with col2:
                                if ocr_sgpa is not None:
                                    sgpa_diff = abs(db_sgpa - ocr_sgpa)
                                    sgpa_match = sgpa_diff < 0.1
                                    st.metric(
                                        "SGPA Comparison", 
                                        f"DB: {db_sgpa:.2f}",
                                        delta=f"OCR: {ocr_sgpa:.2f}",
                                        delta_color="normal" if sgpa_match else "inverse"
                                    )
                                    if not sgpa_match:
                                        st.error(f"โš ๏ธ SGPA Mismatch! Difference: {sgpa_diff:.2f}")
                                else:
                                    st.metric("SGPA (DB)", f"{db_sgpa:.2f}")
                            
                            with col3:
                                if ocr_cgpa is not None:
                                    cgpa_diff = abs(db_cgpa - ocr_cgpa)
                                    cgpa_match = cgpa_diff < 0.1
                                    st.metric(
                                        "CGPA Comparison",
                                        f"DB: {db_cgpa:.2f}",
                                        delta=f"OCR: {ocr_cgpa:.2f}",
                                        delta_color="normal" if cgpa_match else "inverse"
                                    )
                                    if not cgpa_match:
                                        st.error(f"โš ๏ธ CGPA Mismatch! Difference: {cgpa_diff:.2f}")
                                else:
                                    st.metric("CGPA (DB)", f"{db_cgpa:.2f}")
                            
                            # Overall GPA verification status
                            if ocr_sgpa or ocr_cgpa:
                                gpa_matches = []
                                if ocr_sgpa and abs(db_sgpa - ocr_sgpa) < 0.1:
                                    gpa_matches.append("SGPA")
                                if ocr_cgpa and abs(db_cgpa - ocr_cgpa) < 0.1:
                                    gpa_matches.append("CGPA")
                                
                                if len(gpa_matches) == 2:
                                    st.success("โœ… SGPA and CGPA match database records")
                                elif len(gpa_matches) == 1:
                                    st.warning(f"โš ๏ธ Only {gpa_matches[0]} matches. Please verify manually.")
                                else:
                                    st.error("โŒ SGPA/CGPA do not match database. Possible forgery!")
                        
                        # Show subjects table
                        st.dataframe(
                            df[['subject_code', 'subject_name', 'credits_registered', 'grade', 'grade_points']],
                            use_container_width=True,
                            hide_index=True
                        )
                        
                        st.success(f"โœ… Found {len(subjects)} subjects in database for this student")
                    else:
                        st.info("โ„น๏ธ No subject-level data available for this registration number")
                except Exception as e:
                    st.warning(f"โš ๏ธ Could not load subject data: {e}")
        
        # Raw OCR text
        with st.expander("๐Ÿ“„ Raw OCR Text"):
            st.text(ocr_data.get('raw_text', 'No text extracted'))
    
    # Show cropped seals if available
    if st.session_state.cropped_seals:
        with st.expander("๐Ÿ” Detected Seals/Stamps", expanded=True):
            st.write(f"Found {len(st.session_state.cropped_seals)} seal(s) in the certificate:")
            
            cols = st.columns(min(3, len(st.session_state.cropped_seals)))
            for i, seal_info in enumerate(st.session_state.cropped_seals):
                with cols[i % 3]:
                    st.image(seal_info['pil_image'], caption=f"Seal {i+1} ({seal_info['method']} detection)")

def create_verification_report(result, seal_result=None):
    """Create a downloadable verification report."""
    
    # Determine final decision with improved logic
    ocr_status = "Pass" if result['decision'] == 'AUTHENTIC' else "Fail"
    seal_status = seal_result.get('status', 'Not Checked') if seal_result else 'Not Checked'
    
    # Apply same security-first decision logic as above
    ocr_confidence = result.get('final_score', 0)
    seal_confidence = seal_result.get('confidence', 0) if seal_result else 0
    
    # Security-first decision criteria:
    both_pass = (ocr_status == "Pass" and seal_status == "Pass")
    
    # CRITICAL SECURITY CHECK: If fake seals detected with high confidence, REJECT
    fake_seals_detected = False
    rejection_reason = None
    if seal_result and seal_result.get('details'):
        fake_count = seal_result['details'].get('fake_seals', 0)
        total_seals = seal_result['details'].get('total_seals', 0)
        if fake_count > 0 and seal_confidence > 0.7:  # High confidence fake detection
            fake_seals_detected = True
            rejection_reason = f"High confidence fake seal detection ({seal_confidence:.1%})"
    
    # REJECT if fake seals detected with high confidence
    if fake_seals_detected:
        final_decision = "Fake"
    else:
        # Only pass if both OCR and seals pass, or if no seal verification was performed
        if seal_result is None:  # No seal verification
            final_decision = "Real" if (ocr_status == "Pass" and ocr_confidence > 0.8) else "Fake"
        else:  # Seal verification was performed
            final_decision = "Real" if both_pass else "Fake"
    
    report = {
        'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
        'final_decision': final_decision,
        'ocr_verification': {
            'status': ocr_status,
            'decision': result['decision'],
            'confidence_score': result['final_score'],
            'registration_number': result['registration_no'],
            'database_match': result['db_record'] is not None,
            'details': result
        },
        'seal_verification': seal_result if seal_result else {
            'status': 'Not Checked',
            'reason': 'Seal verification was not performed'
        },
        'summary': {
            'final_decision': final_decision,
            'ocr_status': ocr_status,
            'seal_status': seal_status,
            'overall_confidence': (result['final_score'] + seal_result.get('confidence', 0.5)) / 2 if seal_result else result['final_score']
        }
    }

    return json.dumps(report, indent=2, ensure_ascii=False)

def main():
    """Main Streamlit application."""
    
    init_session_state()
    
    st.title("๐ŸŽ“ Certificate Verification System")
    st.markdown("Upload a certificate image to verify its authenticity against our database.")
    
    # Sidebar configuration
    with st.sidebar:
        st.header("โš™๏ธ Configuration")
        
        # System Status
        st.subheader("๐Ÿ”ง System Status")
        
        # OCR Status
        if OCR_AVAILABLE:
            api_key = os.getenv('OCRSPACE_API_KEY')
            if api_key:
                st.success("โœ… OCR API Available & Configured")
            else:
                st.warning("โš ๏ธ OCR API Available (No API Key)")
        else:
            st.error("โŒ OCR Components Not Available")
        
        # Seal Detection Status
        if SEAL_DETECTION_AVAILABLE:
            if SEAL_METHOD == "YOLOv8":
                st.success(f"๐Ÿš€ Seal Detection ({SEAL_METHOD}) - 99% Accuracy")
                st.caption("State-of-the-art AI model trained on your dataset")
            else:
                st.success(f"โœ… Seal Detection ({SEAL_METHOD})")
        else:
            st.error("โŒ Seal Detection Not Available")
            
        # AI Model Status
        if VIT_AVAILABLE:
            st.success("โœ… AI Seal Classifier Available")
        else:
            st.warning("โš ๏ธ AI Model - Demo Mode Only")
        
        # Database status
        db_path = "certs.db"
        if os.path.exists(db_path):
            st.success("โœ… Database connected")
            
            # Show database stats
            import sqlite3
            try:
                conn = sqlite3.connect(db_path)
                cursor = conn.cursor()
                cursor.execute("SELECT COUNT(*) FROM certificates")
                count = cursor.fetchone()[0]
                st.info(f"๐Ÿ“Š {count} certificates in database")
                conn.close()
            except:
                st.warning("โš ๏ธ Database error")
        else:
            st.error("โŒ Database not found")
            st.write("Please run `python init_db.py` first")
        
        # OCR Settings
        st.subheader("๐Ÿ”ง OCR Settings")
        ocr_language = st.selectbox("Language", ["eng", "ara", "chs", "cht", "cze", "dan", "dut", "fin", "fre", "ger", "hun", "ita", "jpn", "kor", "nor", "pol", "por", "rus", "slv", "spa", "swe", "tur"])
        use_overlay = st.checkbox("Extract bounding boxes", value=True)
        
        # Demo Mode
        st.subheader("๐ŸŽฎ Demo Mode")
        demo_mode = st.checkbox("Use Demo Mode (Skip OCR)", help="Test verification with sample OCR data")
        
        # Seal Verification Settings
        st.subheader("๐Ÿ”Ž Seal Verification")
        
        if SEAL_DETECTION_AVAILABLE:
            enable_seal_verification = st.checkbox("Enable Seal Verification", value=True, help="Detect and verify seals/stamps using AI")
            
            if enable_seal_verification:
                # Check if ViT model exists OR if we have HuggingFace URL configured
                model_exists = os.path.exists('vit_seal_checker.pth') and VIT_AVAILABLE
                
                # Check if we can download from HuggingFace
                can_download = False
                try:
                    vit_url = st.secrets.get("VIT_MODEL_URL", None)
                    if vit_url:
                        can_download = True
                except:
                    pass
                
                if model_exists:
                    st.success("โœ… ViT model ready (local)")
                    seal_demo_mode = st.checkbox("Seal Demo Mode", value=False, help="Use demo predictions instead of trained model")
                elif can_download and VIT_AVAILABLE:
                    st.info("๐Ÿ“ฅ ViT model will download from Hugging Face on first use")
                    seal_demo_mode = st.checkbox("Seal Demo Mode", value=False, help="Use demo predictions instead of trained model")
                else:
                    st.warning("โš ๏ธ ViT model not available")
                    st.info("Using demo mode for seal classification")
                    seal_demo_mode = True
        else:
            st.warning("โš ๏ธ Seal verification not available")
            st.info("Install opencv-python-headless to enable seal detection")
            enable_seal_verification = False
            seal_demo_mode = True
        
        # OCR Demo Mode
        st.subheader("๐Ÿ”ค OCR Settings")
        if not OCR_AVAILABLE or not os.getenv('OCRSPACE_API_KEY'):
            st.warning("โš ๏ธ Using OCR Demo Mode")
            st.info("Configure API key for real OCR extraction")
            ocr_demo_mode = True
        else:
            ocr_demo_mode = st.checkbox("OCR Demo Mode", value=False, help="Use sample OCR data instead of API")
    
    # Main interface
    if not OCR_AVAILABLE and not ocr_demo_mode:
        st.error("๐Ÿšจ **Setup Required**: OCR components not available.")
        st.info("๐Ÿ’ก **Alternative**: OCR Demo Mode is automatically enabled for testing")
        ocr_demo_mode = True
    
    # YOLOv8 Integration Check
    if SEAL_METHOD == "YOLOv8":
        try:
            from yolo_seal_detector import check_yolo_integration
            if not check_yolo_integration():
                st.info("๐Ÿ“ฅ **YOLOv8 Setup**: Download the trained model from Kaggle for best seal detection")
        except ImportError:
            pass
    
    if not os.path.exists(db_path) and not ocr_demo_mode:
        st.error("๐Ÿšจ **Setup Required**: Please initialize the database first.")
        st.code("python init_db.py")
        st.info("๐Ÿ’ก **Alternative**: Demo mode will work without database")
        return
    
    # OCR Troubleshooting
    with st.expander("๐Ÿ”ง OCR Troubleshooting Guide"):
        st.markdown("""
        **If you're getting E301 errors:**
        
        1. **โœ… Try Demo Mode**: Enable in sidebar to test verification without OCR
        2. **๐Ÿ“ธ Image Quality**: Use clear, well-lit, straight-aligned certificates
        3. **๐Ÿ“ File Format**: JPG/PNG work best (avoid PDF, TIFF)
        4. **๐Ÿ“ File Size**: Keep under 1MB (system auto-resizes but quality matters)
        5. **๐ŸŽฏ Text Clarity**: Ensure certificate text is readable and high-contrast
        
        **Demo Mode includes sample certificates:**
        - Saksham Sharma (ABC2023001) - DevLabs Institute
        - Prisha Verma (ABC2022007) - Global Tech University
        
        Upload any image and enable Demo Mode to see how verification works!
        """)
    
    # File upload
    uploaded_file = st.file_uploader(
        "Choose a certificate image",
        type=['png', 'jpg', 'jpeg', 'pdf'],
        help="Upload a clear image of the certificate you want to verify"
    )
    
    if uploaded_file is not None:
        st.session_state.uploaded_file = uploaded_file
        
        # Display uploaded image
        if uploaded_file.type.startswith('image'):
            image = Image.open(uploaded_file)
            st.image(image, caption="Uploaded Certificate", use_container_width=True)
        
        # Verify button
        col1, col2, col3 = st.columns([1, 1, 2])
        
        with col1:
            if st.button("๐Ÿ” Verify Certificate", type="primary"):
                verify_certificate(uploaded_file, ocr_language, use_overlay, ocr_demo_mode, 
                                 enable_seal_verification, seal_demo_mode if enable_seal_verification else False)
        
        with col2:
            if st.session_state.verification_result:
                report_json = create_verification_report(st.session_state.verification_result, st.session_state.seal_result)
                st.download_button(
                    "๐Ÿ“ฅ Download Report",
                    data=report_json,
                    file_name=f"verification_report_{int(time.time())}.json",
                    mime="application/json"
                )
    
    # Display results
    if st.session_state.verification_result:
        st.markdown("---")
        display_verification_result(st.session_state.verification_result, st.session_state.seal_result)
        
        # Option to verify another certificate
        if st.button("๐Ÿ”„ Verify Another Certificate"):
            st.session_state.verification_result = None
            st.session_state.ocr_result = None
            st.session_state.seal_result = None
            st.session_state.cropped_seals = None
            st.session_state.uploaded_file = None
            st.rerun()

def verify_certificate(uploaded_file, language, use_overlay, ocr_demo_mode=False, enable_seal_verification=True, seal_demo_mode=False):
    """Process the certificate verification."""
    
    try:
        # Show progress
        progress_bar = st.progress(0)
        status_text = st.empty()
        
        status_text.text("๐Ÿ“ค Processing file...")
        progress_bar.progress(5)
        
        # Read file data (reset file pointer first)
        uploaded_file.seek(0)
        file_bytes = uploaded_file.read()
        
        if len(file_bytes) == 0:
            st.error("โŒ File appears to be empty or corrupted. Please try uploading again.")
            progress_bar.empty()
            status_text.empty()
            return
        
        # Save uploaded file temporarily for seal detection
        temp_image_path = None
        if enable_seal_verification and uploaded_file.type.startswith('image'):
            temp_dir = tempfile.mkdtemp()
            temp_image_path = os.path.join(temp_dir, f"temp_cert_{int(time.time())}.{uploaded_file.name.split('.')[-1]}")
            
            with open(temp_image_path, 'wb') as f:
                f.write(file_bytes)
        
        if ocr_demo_mode:
            # Use demo OCR data
            status_text.text("๐ŸŽฎ Using demo OCR data...")
            progress_bar.progress(30)
            
            # Sample OCR result based on filename or random selection
            demo_certificates = {
                "saksham": {
                    'success': True,
                    'extracted_text': '''CERTIFICATE OF COMPLETION
                    
This is to certify that

SAKSHAM SHARMA

has successfully completed the course

B.Tech Computer Engineering

from

DevLabs Institute

in the year 2023

Registration Number: ABC2023001

Date of Issue: December 2023''',
                    'confidence': 0.92,
                    'bounding_boxes': []
                },
                "prisha": {
                    'success': True,
                    'extracted_text': '''GRADUATION CERTIFICATE
                    
This certifies that

PRISHA VERMA

has completed

M.Tech AI

from

Global Tech University

Year: 2022

Registration: ABC2022007''',
                    'confidence': 0.88,
                    'bounding_boxes': []
                }
            }
            
            # Select demo data based on filename
            filename_lower = uploaded_file.name.lower()
            if 'saksham' in filename_lower or 'abc2023001' in filename_lower:
                ocr_result = demo_certificates["saksham"]
            elif 'prisha' in filename_lower or 'abc2022007' in filename_lower:
                ocr_result = demo_certificates["prisha"]
            else:
                # Default to Saksham's certificate
                ocr_result = demo_certificates["saksham"]
                
            st.info("๐ŸŽฎ Demo Mode: Using sample OCR data for testing")
            
        else:
            # Real OCR processing
            status_text.text("๐Ÿ” Running OCR analysis...")
            progress_bar.progress(20)
            
            # Run OCR
            if OCRClient:
                ocr_client = OCRClient()
                ocr_result = ocr_client.extract_text_from_bytes(
                    file_bytes,
                    language=language,
                    overlay=use_overlay
                )
            else:
                # Fallback to demo mode if OCR not available
                ocr_result = {'success': False, 'error': 'OCR components not available'}
        
        st.session_state.ocr_result = ocr_result
        
        if not ocr_result['success']:
            st.error(f"โŒ OCR failed: {ocr_result.get('error', 'Unknown error')}")
            if not ocr_demo_mode:
                st.info("๐Ÿ’ก **Tip**: Try enabling 'Demo Mode' in the sidebar to test the verification system without OCR")
            progress_bar.empty()
            status_text.empty()
            return
        
        status_text.text("๐Ÿ” Verifying against database...")
        progress_bar.progress(50)
        
        # Run OCR verification
        if CertificateVerifier:
            verifier = CertificateVerifier()
            st.session_state.verifier = verifier  # Store for later use
            verification_result = verifier.verify_certificate(ocr_result, uploaded_file.name)
        else:
            # Demo mode verification result
            verification_result = {
                'decision': 'AUTHENTIC',
                'confidence': 0.85,
                'field_scores': {'name': 0.95, 'course': 0.80, 'institution': 0.90},
                'db_record': {'reg_no': 'DEMO001', 'name': 'Demo Certificate', 'status': 'valid'}
            }
        
        st.session_state.verification_result = verification_result
        
        # Step 2: Seal Verification with YOLOv8
        seal_result = None
        if enable_seal_verification and temp_image_path:
            status_text.text("๐Ÿ”Ž Detecting and verifying seals with AI...")
            progress_bar.progress(70)
            
            try:
                # Initialize seal detector
                seal_detector = SealDetector()
                
                if seal_demo_mode:
                    # Use demo seal verification
                    if VIT_AVAILABLE:
                        classifier = ViTSealClassifier()
                        seal_result = classifier.create_dummy_prediction(confidence=0.82)
                    else:
                        seal_result = {
                            "step": "Seal Verification",
                            "status": "Pass",
                            "reason": "Demo mode - seal appears authentic",
                            "seal_status": "Real",
                            "confidence": 0.82
                        }
                    st.session_state.cropped_seals = []  # No actual cropped seals in demo mode
                    
                    # Show demo seal info
                    st.info("๐ŸŽฎ Demo Mode: Using simulated seal detection results")
                    
                else:
                    # Real YOLOv8 seal detection and verification
                    st.write("**๐Ÿค– YOLOv8 Seal Detection in Progress...**")
                    
                    # Get detection summary with Streamlit integration
                    summary = seal_detector.get_detection_summary(temp_image_path, confidence_threshold=0.5)
                    
                    # Visualize detections if available
                    if hasattr(seal_detector, 'visualize_detections'):
                        detected_image = seal_detector.visualize_detections(temp_image_path)
                        if detected_image:
                            st.image(detected_image, caption="๐ŸŽฏ AI-Detected Seals", use_container_width=True)
                    
                    # Process seal detection results
                    if summary['total_seals'] > 0:
                        # Analyze detection results
                        fake_count = summary['class_distribution'].get('fake', 0)
                        true_count = summary['class_distribution'].get('true', 0)
                        avg_confidence = summary['average_confidence']
                        
                        # Determine overall seal authenticity
                        if fake_count > true_count:
                            seal_status = "Fake"
                            status = "Fail"
                            reason = f"Detected {fake_count} fake seals vs {true_count} authentic seals"
                        elif true_count > 0 and fake_count == 0:
                            seal_status = "Real"
                            status = "Pass"
                            reason = f"All {true_count} detected seals appear authentic"
                        else:
                            seal_status = "Suspicious"
                            status = "Warning"
                            reason = f"Mixed results: {true_count} authentic, {fake_count} fake seals"
                        
                        # Crop seals for further analysis
                        cropped_seals = seal_detector.crop_seals_from_image(temp_image_path)
                        st.session_state.cropped_seals = []
                        
                        # Convert cropped seals to expected format
                        for i, cropped_path in enumerate(cropped_seals):
                            if os.path.exists(cropped_path):
                                from PIL import Image
                                seal_img = Image.open(cropped_path)
                                detection = summary['detections'][i] if i < len(summary['detections']) else {}
                                
                                st.session_state.cropped_seals.append({
                                    'pil_image': seal_img,
                                    'path': cropped_path,
                                    'method': f"YOLOv8 ({detection.get('class', 'unknown')})",
                                    'confidence': detection.get('confidence', 0.0),
                                    'class': detection.get('class', 'unknown')
                                })
                        
                        seal_result = {
                            "step": "Seal Verification",
                            "status": status,
                            "reason": reason,
                            "seal_status": seal_status,
                            "confidence": avg_confidence,
                            "details": {
                                "total_seals": summary['total_seals'],
                                "fake_seals": fake_count,
                                "authentic_seals": true_count,
                                "detection_method": "YOLOv8",
                                "model_confidence": avg_confidence
                            }
                        }
                        
                        # Show detailed results
                        if status == "Pass":
                            st.success(f"โœ… {reason} (confidence: {avg_confidence:.1%})")
                        elif status == "Fail":
                            st.error(f"โŒ {reason} (confidence: {avg_confidence:.1%})")
                        else:
                            st.warning(f"โš ๏ธ {reason} (confidence: {avg_confidence:.1%})")
                    
                    else:
                        # No seals detected
                        seal_result = {
                            "step": "Seal Verification",
                            "status": "Warning",
                            "reason": "No seals detected in certificate - this may indicate a fake certificate",
                            "seal_status": "Missing",
                            "confidence": 0.0,
                            "details": {
                                "total_seals": 0,
                                "detection_method": "YOLOv8"
                            }
                        }
                        st.session_state.cropped_seals = []
                        st.warning("โš ๏ธ No seals detected - certificates usually contain official seals/stamps")
                
            except Exception as e:
                st.error(f"โŒ Seal verification error: {str(e)}")
                seal_result = {
                    "step": "Seal Verification", 
                    "status": "Error",
                    "reason": f"Seal verification failed: {str(e)}",
                    "seal_status": "Error",
                    "confidence": 0.0
                }
            
            # Clean up temp file
            try:
                if os.path.exists(temp_image_path):
                    os.remove(temp_image_path)
                    os.rmdir(os.path.dirname(temp_image_path))
            except:
                pass
        
        st.session_state.seal_result = seal_result
        
        status_text.text("โœ… Verification complete!")
        progress_bar.progress(100)
        
        # Clear progress indicators
        time.sleep(1)
        progress_bar.empty()
        status_text.empty()
        
        # Show success message with improved decision logic
        ocr_status = "Pass" if verification_result['decision'] == 'AUTHENTIC' else "Fail"
        seal_status = seal_result.get('status', 'Unknown') if seal_result else 'Not Checked'
        
        # Apply same security-first decision logic
        ocr_confidence = verification_result.get('final_score', 0)
        seal_confidence = seal_result.get('confidence', 0) if seal_result else 0
        
        # Security-first decision criteria:
        both_pass = (ocr_status == "Pass" and seal_status == "Pass")
        
        # CRITICAL SECURITY CHECK: If fake seals detected with high confidence, REJECT
        fake_seals_detected = False
        rejection_reason = None
        if seal_result and seal_result.get('details'):
            fake_count = seal_result['details'].get('fake_seals', 0)
            total_seals = seal_result['details'].get('total_seals', 0)
            if fake_count > 0 and seal_confidence > 0.7:  # High confidence fake detection
                fake_seals_detected = True
                rejection_reason = f"High confidence fake seal detection ({seal_confidence:.1%})"
        
        # REJECT if fake seals detected with high confidence
        if fake_seals_detected:
            final_decision = "Fake"
        else:
            # Only pass if both OCR and seals pass, or if no seal verification was performed
            if seal_result is None:  # No seal verification
                final_decision = "Real" if (ocr_status == "Pass" and ocr_confidence > 0.8) else "Fake"
            else:  # Seal verification was performed
                final_decision = "Real" if both_pass else "Fake"
        
        if final_decision == "Real":
            st.success("๐ŸŽ‰ Certificate verification completed - AUTHENTIC!")
        else:
            if rejection_reason:
                st.error(f"โŒ Certificate verification failed - {rejection_reason}")
            else:
                st.error("โŒ Certificate verification failed - verification issues detected.")
        
        if seal_result and enable_seal_verification:
            st.info(f"๐Ÿ”Ž Seal verification: {seal_result.get('seal_status', 'Unknown')}")
    
    except Exception as e:
        st.error(f"๐Ÿ’ฅ Verification failed: {str(e)}")
        # Clear progress indicators
        if 'progress_bar' in locals():
            progress_bar.empty()
        if 'status_text' in locals():
            status_text.empty()

if __name__ == "__main__":
    main()