from datetime import datetime
import re
import cv2
import numpy as np
from google.cloud import vision_v1
import pkg_resources
from idvpackage.ocr import IdentityVerification
from PIL import Image
import base64

def create_final_result(dictionary):
    result = ""
    for key, value in dictionary.items():
        if isinstance(value, dict):
            sub_result = create_final_result(value)
            if sub_result == 'consider':
                return 'consider'
            elif sub_result == 'clear':
                result = 'clear'
        elif value in ['clear', 'consider', ""]:
            if value == 'consider':
                return 'consider'
            elif result != 'clear':
                result = value
    return result

def age_validation(dob, age_threshold=18):
    age_val = {
        "breakdown": {
            "minimum_accepted_age": {
            "properties": {},
            "result": ""
            }
        },
        "result": ""
        }
    
    dob_date = datetime.strptime(dob, "%d/%m/%Y")

    current_date = datetime.now()

    age = current_date.year - dob_date.year - ((current_date.month, current_date.day) < (dob_date.month, dob_date.day))

    if age>=age_threshold:
        age_val["breakdown"]["minimum_accepted_age"]["result"] = "clear"
        age_val["result"] = "clear"
    else:
        age_val["breakdown"]["minimum_accepted_age"]["result"] = "consider"
        age_val["result"] = "consider"

    return age_val
    
def created_at():
    current_datetime = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')

    return current_datetime

def is_valid_and_not_expired(expiry_date):
    try:
        parsed_date = datetime.strptime(expiry_date, "%d/%m/%Y")
        current_date = datetime.now()
        
        if parsed_date < current_date:
            return 'consider'
        return 'clear'
    
    except:
        return 'consider'

def identify_document_type(text):
    text = text.upper()
    emirates_id_pattern = r'\b(ILARE\w*|IDARE\w*|RESIDENT IDENTITY)\b'
    passport_pattern = r'\b(PASSPORT|PPT)\b'
    driver_license_pattern = r'\b(DRIVER|LICENSE|DL)\b'

    if re.search(emirates_id_pattern, text):
        return "EID"

    if re.search(passport_pattern, text):
        return "PASSPORT"

    if re.search(driver_license_pattern, text):
        return "DL"

    return "Unknown"

def identify_front_id(text):
    front_id_keywords = ['Resident Identity', 'ID Number']
    pattern = '|'.join(map(re.escape, front_id_keywords))
    
    if re.search(pattern, text, re.IGNORECASE):
        return True
    else:
        return False

def identify_back_id(text):
    back_id_keywords = ['ILARE', 'IDARE', 'Signature']
    pattern = '|'.join(map(re.escape, back_id_keywords))
    
    if re.search(pattern, text, re.IGNORECASE):
        return True
    else:
        return False

def document_on_printed_paper(image, edge_threshold=100, contour_area_threshold=5000):
    image = IdentityVerification.image_conversion(image)
    
    image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    image_blurred = cv2.GaussianBlur(image_gray, (5, 5), 0)
    image_edges = cv2.Canny(image_blurred, edge_threshold, edge_threshold * 2)

    image_contours, _ = cv2.findContours(image_edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    image_paper_edges = [contour for contour in image_contours if cv2.contourArea(contour) > contour_area_threshold]

    # print(front_paper_edges, back_paper_edges)

    if len(image_paper_edges) > 0:
        return 'consider'
    else:
        return 'clear'

def detect_logo(client, front_id):
    reference_logo_path = pkg_resources.resource_filename('idvpackage', 'emirates_id_logo.jpeg')
    
    with open(reference_logo_path, 'rb') as logo_file:
        reference_image_content = logo_file.read()

    input_image_content = IdentityVerification.image_conversion(front_id)
    
    reference_image = vision_v1.types.Image(content=reference_image_content)
    input_image = vision_v1.types.Image(content=input_image_content)

    reference_response = client.logo_detection(image=reference_image)
    input_response = client.logo_detection(image=input_image)

    reference_logos = reference_response.logo_annotations
    input_logos = input_response.logo_annotations

    for reference_logo in reference_logos:
        for input_logo in input_logos:
            if reference_logo.description.lower() == input_logo.description.lower():
                return 'clear'

    return 'consider'

def perform_feature_matching(image, template_image_path):
    template_image = cv2.imread(template_image_path, cv2.IMREAD_COLOR)

    sift = cv2.SIFT_create()

    kp1, des1 = sift.detectAndCompute(template_image, None)
    kp2, des2 = sift.detectAndCompute(image, None)

    if len(kp1) == 0 or len(kp2) == 0:
        return 0.0 
    
    bf = cv2.BFMatcher()
    matches = bf.knnMatch(des1, des2, k=2)

    good_matches = []
    for m, n in matches:
        if m.distance < 0.75 * n.distance:
            good_matches.append(m)

    similarity_score = len(good_matches) / len(kp1)

    return similarity_score

# part of detecting screenshot - checks for time values seen on phone
def extract_time_values(text_annotations):
    time_values = []
    for annotation in text_annotations:
        text = annotation.description
        time_matches = re.findall(r'\d{1,2}:\d{2}', text)
        time_values.extend(time_matches)
    return time_values

def detect_screenshot(client, image):
    icons = 0
    battery_value_and_time = 0

    similarity_threshold = 0.45

    for filename in pkg_resources.resource_listdir('idvpackage', 'icons'):
        if filename.endswith('.png'):
            icon_path = pkg_resources.resource_filename('idvpackage', f'icons/{filename}')
            image_data = np.frombuffer(base64.b64decode(image), dtype=np.uint8)

            front_similarity_score = perform_feature_matching(image_data, icon_path)
            if front_similarity_score >= similarity_threshold:
                icons+=1

    image_data = vision_v1.Image(content=base64.b64decode(image))

    image_response = client.text_detection(image=image_data)

    image_text_annotations = image_response.text_annotations

    image_time_values = extract_time_values(image_text_annotations)

    if image_time_values:
        battery_value_and_time+=1

    if icons or battery_value_and_time:
        return 'consider'
    else:
        return 'clear'

def detect_photo_on_screen(client, image):
    flag = 'clear'

    image_data = vision_v1.Image(content=base64.b64decode(image))
    
    image_response = client.label_detection(image=image_data)
    image_labels = image_response.label_annotations

    confidence_threshold = 0.70  

    keywords = ['mobile phone', "mobile device", "portable communications device", 'communication device', 'smartphone', 'cell phone', "hand", 'touchscreen', 'laptop', 'notebook', 'computer', 'screen', 'finger', 'thumb', 'gadget']

    for label in image_labels:
        description = label.description.lower()
        confidence = label.score
        if confidence >= confidence_threshold and any(keyword.lower() in description for keyword in keywords):
            flag = 'consider'

    return flag

## pending - change image read logic
def detect_template(front_id, back_id):
    pass

## for data comparison, accept data from dev and match with data extracted from id
def data_comparison_check():
    ## sending default as clear - identified from current nym's table results
    data_comparison = {
      "breakdown": {
        "date_of_birth": {
          "properties": {}
        },
        "date_of_expiry": {
          "properties": {}
        },
        "document_numbers": {
          "properties": {}
        },
        "document_type": {
          "properties": {}
        },
        "first_name": {
          "properties": {}
        },
        "gender": {
          "properties": {}
        },
        "issuing_country": {
          "properties": {}
        },
        "last_name": {
          "properties": {}
        }
      }
    }

    return data_comparison

def data_consistency_check(data, front_id_text, back_id_text):
    data_consistency = {
      "breakdown": {
        "date_of_birth": {
          "properties": {},
          "result": "clear"
        },
        "date_of_expiry": {
          "properties": {},
          "result": "clear"
        },
        "document_numbers": {
          "properties": {},
          "result": "clear"
        },
        "document_type": {
          "properties": {},
          "result": "clear"
        },
        "first_name": {
          "properties": {},
          "result": "clear"
        },
        "gender": {
          "properties": {},
          "result": "clear"
        },
        "issuing_country": {
          "properties": {},
          "result": "clear"
        },
        "last_name": {
          "properties": {},
          "result": "clear"
        },
        "multiple_data_sources_present": {
          "properties": {},
          "result": "clear"
        },
        "nationality": {
          "properties": {},
          "result": "clear"
        }
      },
      "result": "clear"
    }

    #### For data consistency compare data from different sources, like id and passport. 
    #### so the dob from id should match with dob extracted from passport

    doc_type1 = identify_document_type(front_id_text)
    doc_type2 = identify_document_type(back_id_text)
    if doc_type1 == 'EID' or doc_type2=='EID':
        data_consistency['breakdown']['document_type']['result'] = 'clear'
    else:
        data_consistency['breakdown']['document_type']['result'] = 'consider'
        data_consistency['result'] = 'consider'
    
    return data_consistency

def data_validation_check(data):
    data_validation = {
    "breakdown": {
        "date_of_birth": {
            "properties": {},
            "result": "clear"
        },
        "document_expiration": {
            "properties": {},
            "result": "clear"
        },
        "document_numbers": {
            "properties": {},
            "result": "clear"
        },
        "expiry_date": {
            "properties": {},
            "result": "clear"
        },
        "gender": {
            "properties": {},
            "result": "clear"
        },
        "mrz": {
            "properties": {},
            "result": "clear"
        },
        "barcode": {
            "properties": {},
            "result": "clear"
        }
    },
    "result": "clear"
}

    try:
        dob = data.get('dob')
        parsed_date = datetime.strptime(dob, "%d/%m/%Y")
        data_validation["breakdown"]['date_of_birth']["result"] = 'clear'
    except ValueError:
        data_validation["breakdown"]['date_of_birth']["result"] = 'consider'

    doc_no = data.get('card_number')
    if len(doc_no)==9:
        data_validation["breakdown"]['document_numbers']["result"] = 'clear'
    else:
        data_validation["breakdown"]['document_numbers']["result"] = 'consider'

    try:
        doe = data.get('expiry_date')
        parsed_date = datetime.strptime(doe, "%d/%m/%Y")
        data_validation["breakdown"]['expiry_date']["result"] = 'clear'
    except ValueError:
        data_validation["breakdown"]['expiry_date']["result"] = 'consider'

    gender = data.get('gender')
    if gender.isalpha() and len(gender) == 1:
        data_validation["breakdown"]['gender']["result"] = 'clear'
    else:
        data_validation["breakdown"]['gender']["result"] = 'consider'
    
    doe = data.get('expiry_date')
    expiry_result = is_valid_and_not_expired(doe)
    data_validation['breakdown']['document_expiration'] = expiry_result
    
    mrz = data.get('mrz')
    mrz1 = data.get('mrz1')
    mrz2 = data.get('mrz2')
    mrz3 = data.get('mrz3')
    if len(mrz) == 1 and mrz1 and mrz2 and mrz3:
        data_validation["breakdown"]['mrz']["result"] = 'clear'
    else:
        data_validation["breakdown"]['mrz']["result"] = 'consider'
    
    result = create_final_result(data_validation)
    data_validation['result'] = result

    # if data_validation["breakdown"]['date_of_birth']["result"]=='clear' and data_validation["breakdown"]['expiry_date']["result"]=='clear' and data_validation["breakdown"]['gender']["result"]=='clear' and data_validation["breakdown"]['mrz']["result"]=='clear':
    #     data_validation['result'] = 'clear'

    return data_validation

## pending
def image_integrity_check(data, front_id_text, back_id_text, coloured, blurred, glare, missing_fields):
    image_integrity = {
      "breakdown": {
        "colour_picture": {
          "properties": {},
          "result": coloured
        },
        "conclusive_document_quality": {
          "properties": {
              "missing_back": "clear",
              "digital_document": "clear", ## digital or printed
              "punctured_document": "clear", ## EDC
              "corner_removed": is_valid_and_not_expired(data.get('expiry_date')), 
              "watermarks_digital_text_overlay": "clear",
              "abnormal_document_features": blurred,
              "obscured_security_features": "clear", ## not enough info
              # TODO: font rec
              "obscured_data_points": "clear" ## font recognition
            },
          "result": "clear"
        },
        "image_quality": {
          "properties": {
              "blurred_photo": blurred,
              "covered_photo": missing_fields,
              "cut_off_document": missing_fields,
              "glare_on_photo": glare,
              "other_photo_issue": missing_fields,
              # TODO: pending, check for multiple docs
              "two_documents_uploaded": "clear"
          },
          "result": "clear"
        },
        "supported_document": {
          "properties": {},
          "result": "clear"
        }
      },
      "result": "clear"
    }

    if back_id_text and identify_back_id(back_id_text):
        image_integrity['breakdown']['conclusive_document_quality']['properties']['missing_back'] = 'clear'
    else:
        image_integrity['breakdown']['conclusive_document_quality']['properties']['missing_back'] = 'consider'

    f_result = identify_front_id(front_id_text)
    b_result = identify_back_id(back_id_text)

    if f_result and b_result:
        image_integrity['breakdown']['supported_document']['result'] = 'clear'
    else:
        image_integrity['breakdown']['supported_document']['result'] = 'consider'

    front_doc_on_pp = data.get('front_doc_on_pp')
    back_doc_on_pp = data.get('doc_on_pp')
    if front_doc_on_pp == 'consider' or back_doc_on_pp == 'consider':
        image_integrity['breakdown']['conclusive_document_quality']['properties']['digital_document'] = 'consider'

    front_logo_res = data.get('front_logo_result')
    if front_logo_res == 'consider':
        image_integrity['breakdown']['conclusive_document_quality']['properties']['watermarks_digital_text_overlay'] = 'consider'
    # image_integrity['breakdown']['image_quality']['properties']['blurred_photo'] = blurred
    # image_integrity['breakdown']['image_quality']['properties']['glare_on_photo'] = glare

    image_quality_result = create_final_result(image_integrity['breakdown']['image_quality'])
    conclusive_document_quality_result = create_final_result(image_integrity['breakdown']['conclusive_document_quality'])
    colour_picture_result = image_integrity['breakdown']['colour_picture']['result']
    supported_documents_result = image_integrity['breakdown']['supported_document']['result']

    if image_quality_result == 'consider' or conclusive_document_quality_result == 'consider' or colour_picture_result == 'consider' or supported_documents_result == 'consider':
        image_integrity['result'] = 'consider'

    return image_integrity

def visual_authenticity_check(data, front_id_text, back_id_text, selfie, facial_similarity):
    visual_authenticity = {
      "breakdown": {
        # TODO conclusive result - tamper detection
        "digital_tampering": {
          "properties": {},
          "result": "clear"
        },
        "face_detection": {
          "properties": {},
          "result": "clear"
        },
        # TODO font recognition - tamper detection
        "fonts": {
          "properties": {},
          "result": "clear"
        },
        "original_document_present": {
          "properties": {
                "scan": "clear",
                "document_on_printed_paper": "clear",
                "screenshot": "clear",
                "photo_of_screen": "clear"
          },
          "result": "clear"
        },
        "other": {
          "properties":  {},
          "result": "clear"
        },
        # TODO compare all 3 faces extracted for similarity
        "picture_face_integrity": {
          "properties": {},
          "result": "clear"
        },
        "security_features": {
          "properties": {},
          "result": "clear"
        },
        # TODO - tamper detection
        "template": {
          "properties": {},
          "result": "clear"
        }
      },
      "result": "clear"
    }

    if selfie:
        if facial_similarity>=0.65:
            visual_authenticity['breakdown']['face_detection'] = 'clear'
            visual_authenticity['breakdown']['security_features'] = 'clear'
        else:
            visual_authenticity['breakdown']['face_detection'] = 'consider'
            visual_authenticity['breakdown']['security_features'] = 'consider'
    else:
        visual_authenticity['breakdown']['face_detection'] = ''
        visual_authenticity['breakdown']['security_features'] = ''
    
    doc_type1 = identify_document_type(front_id_text)
    doc_type2 = identify_document_type(back_id_text)
    if doc_type1 == 'EID' or doc_type2=='EID':
        visual_authenticity['breakdown']['original_document_present']['properties']['scan'] = 'clear'
    else:
        visual_authenticity['breakdown']['original_document_present']['properties']['scan'] = 'consider'
    
    front_doc_on_pp = data.get('front_doc_on_pp')
    back_doc_on_pp = data.get('doc_on_pp')
    if front_doc_on_pp == 'consider' or back_doc_on_pp == 'consider':
        visual_authenticity['breakdown']['original_document_present']['properties']['document_on_printed_paper'] = 'consider'

    front_screenshot = data.get('front_screenshot_result')
    back_screenshot = data.get('screenshot_result')
    if front_screenshot == 'consider' or back_screenshot == 'consider':
        visual_authenticity['breakdown']['original_document_present']['properties']['screenshot'] = 'consider'

    front_photo_on_screen_result = data.get('front_photo_on_screen_result')
    photo_on_screen_result = data.get('photo_on_screen_result')
    if front_photo_on_screen_result == 'consider' or photo_on_screen_result == 'consider':
        visual_authenticity['breakdown']['original_document_present']['properties']['photo_of_screen'] = 'consider'

    final_result = create_final_result(visual_authenticity)
    visual_authenticity['result'] = final_result

    return visual_authenticity

def main_details(data):
    main_properties = {
        ## barcode is empty in all instance - TBD
        'barcode': [],
        "date_of_birth": "",
        "date_of_expiry": "",
        "document_numbers": [],
        "document_type": "",
        "first_name": "",
        "gender": "",
        "issuing_country": "",
        "last_name": "",
        "mrz_line1": "",
        "mrz_line2": "",
        "mrz_line3": "",
        "nationality": ""
    }

    try:
        main_properties['date_of_birth'] = data.get('dob')
        main_properties['date_of_expiry'] = data.get('expiry_date')

        if data.get('card_number'):
            card_data_t = {
            "type": "type",
            "value": "document_number"
            }

            card_data_v = {
                "type": "value",
                "value": data['card_number']
            }

            main_properties['document_numbers'].append(card_data_t)
            main_properties['document_numbers'].append(card_data_v)

        if data.get('id_number'):
            id_data_t = {
                        "type": "type",
                        "value": "personal_number"
                    }
            id_data_v = {
                        "type": "value",
                        "value": data['id_number']
                    }
                
            main_properties['document_numbers'].append(id_data_t) 
            main_properties['document_numbers'].append(id_data_v) 
        
        main_properties['document_type'] = 'national_identity_card'
        main_properties['first_name'] = data.get('name')
        main_properties['gender'] = data.get('gender')
        main_properties['issuing_country'] = data.get('issuing_place')
        main_properties['last_name'] = data.get('name')
        main_properties['mrz_line1'] = data.get('mrz1')
        main_properties['mrz_line2'] = data.get('mrz2')
        main_properties['mrz_line3'] = data.get('mrz3')
        main_properties['nationality'] = data.get('nationality')

    except:
        main_properties

    return main_properties    

def form_final_data_document_report(data, front_id_text, back_id_text, coloured, selfie, facial_similarity, blurred, glare, missing_fields):
    try:
        document_report = {
            ## pending - to be filled by dev
            "_id": "",
            "breakdown": {
                "age_validation": age_validation(data.get('dob')),
                "compromised_document": {
                    "result": "clear"
                    },
                "data_comparison": data_comparison_check(),
                "data_consistency": data_consistency_check(data, front_id_text, back_id_text),
                "data_validation": data_validation_check(data),
                "image_integrity": image_integrity_check(data, front_id_text, back_id_text, coloured, blurred, glare, missing_fields),
                "issuing_authority": {
                "breakdown": {
                    "nfc_active_authentication": {
                    "properties": {}
                    },
                    "nfc_passive_authentication": {
                    "properties": {}
                    }
                }
                },
                "police_record": {},
                "visual_authenticity": visual_authenticity_check(data, front_id_text, back_id_text, selfie, facial_similarity),
                ## pending - to be filled by dev
                "check_id": "", 
                "created_at": created_at(),
                "documents": [
                    {
                    ## pending - id value in table stored in db for front id - to be filled by dev
                    "id": ""
                    },
                    {
                    ## pending - id value in table stored in db for front id - to be filled by dev
                    "id": ""
                    }
                ],
                "name": "document",
                "properties": main_details(data),
                # include all results from above to find this
                "result": "",
                "status": "complete",
                "sub_result": ""
            }
        }
        
        final_result = create_final_result(document_report)
        document_report['breakdown']['result'] = final_result
        document_report['breakdown']['sub_result'] = final_result

        return document_report
    
    except Exception as e:
        return e

def form_final_facial_similarity_report(selfie, facial_similarity, liveness_result):
    facial_report = {
        "created_at": "",
        ## pending - to be filled by dev
        "href": "/v3.6/reports/<REPORT_ID>",
        ## pending - to be filled by dev
        "id": "<REPORT_ID>",
        "name": "facial_similarity_video",
        "properties": {},
        "breakdown": {
            "face_comparison": {
                "breakdown": {
                    "face_match": { 
                        "properties": {
                            "score": 0,
                            ## pending - to be filled by dev
                            "document_id": "<DOCUMENT_ID>"
                        },
                        "result": "clear", 
                    }
                },
                "result": "clear"
            },
            "image_integrity": {
                "breakdown": {
                    "face_detected": {
                        "result": "clear",
                        "properties": {}
                    },
                    ## pending
                    "source_integrity": {
                        "result": "clear",
                        "properties": {}
                    }
                },
                "result": "clear",
            },
            "visual_authenticity": {
                "breakdown": {
                    "liveness_detected": {
                        "properties": {},
                        "result": "clear",
                    },
                    "spoofing_detection": {
                        "properties": {
                            "score": 0.90
                        },
                        "result": "clear",
                    }
                },
                "result": "clear",
            }
        },
        "result": "clear",
        "status": "complete",
        "sub_result": "clear",
        ## pending - to be filled by dev
        "check_id": "<CHECK_ID>",
        "documents": []
        }
    
    facial_report['created_at'] = created_at()
    
    facial_report['breakdown']['face_comparison']['breakdown']['face_match']['properties']['score'] = facial_similarity

    if not selfie:
        facial_report['breakdown']['face_comparison']['breakdown']['face_match']['result'] = ''
        facial_report['breakdown']['face_comparison']['result'] = ''
        facial_report['breakdown']['image_integrity']['breakdown']['face_detected']['result'] = ''
        facial_report['breakdown']['image_integrity']['breakdown']['source_integrity']['result'] = ''

    if selfie and facial_similarity>=0.65:
        facial_report['breakdown']['face_comparison']['breakdown']['face_match']['result'] = 'clear'
        facial_report['breakdown']['face_comparison']['result'] = 'clear'
        facial_report['breakdown']['image_integrity']['breakdown']['face_detected']['result'] = 'clear'
    else:
        facial_report['breakdown']['face_comparison']['breakdown']['face_match']['result']  = 'consider'
        facial_report['breakdown']['face_comparison']['result'] = 'consider'
        facial_report['breakdown']['image_integrity']['breakdown']['face_detected']['result'] = 'consider'
    
    facial_report['breakdown']['visual_authenticity']['breakdown']['spoofing_detection']['properties']['score'] = liveness_result

    if liveness_result == 'consider':
        facial_report['breakdown']['visual_authenticity']['breakdown']['liveness_detected']['result'] = 'consider'
        facial_report['breakdown']['visual_authenticity']['breakdown']['spoofing_detection']['result'] = 'consider'
    elif liveness_result == None:
        facial_report['breakdown']['visual_authenticity']['breakdown']['liveness_detected']['result'] = ''
        facial_report['breakdown']['visual_authenticity']['breakdown']['spoofing_detection']['properties']['score'] = ''
        facial_report['breakdown']['visual_authenticity']['breakdown']['spoofing_detection']['result'] = ''

    if selfie and liveness_result:
        visual_authenticity_final_result = create_final_result(facial_report['breakdown']['visual_authenticity'])
        facial_report['breakdown']['visual_authenticity']['result'] = visual_authenticity_final_result

        image_integrity_final_result = create_final_result(facial_report['breakdown']['image_integrity'])
        facial_report['breakdown']['image_integrity']['result'] = image_integrity_final_result

        complete_final_result = create_final_result(facial_report['breakdown'])
        facial_report['result'] = complete_final_result
        facial_report['sub_result'] = complete_final_result

    return facial_report
