from google.oauth2.service_account import Credentials
from google.cloud import vision_v1
import json
import pycountry
from googletrans import Translator
from PIL import Image, ImageEnhance, ImageOps, ImageFilter
import base64
import io
import numpy as np
import cv2
from datetime import datetime, timedelta
import tempfile
from rapidfuzz import fuzz
import face_recognition
import re
import os
from idvpackage.constants import BRIGHTNESS_THRESHOLD, BLUR_THRESHOLD
from io import BytesIO
import time
import logging
import openai
from concurrent.futures import ThreadPoolExecutor, as_completed
# from idvpackage.blur_detection import is_image_blur


logging.basicConfig(level=logging.INFO)

google_client_dict = {}

class IdentityVerification:

    def __init__(self, credentials_string, api_key=None):
        """
        This is the initialization function of a class that imports a spoof model and loads an OCR
        reader.
        """
        st = time.time()
        credentials_dict = json.loads(credentials_string)
        credentials = Credentials.from_service_account_info(credentials_dict)

        self.client = google_client_dict.get(credentials)
        if not self.client:
            self.client = vision_v1.ImageAnnotatorClient(credentials = credentials)
            google_client_dict[credentials] = self.client

        openai.api_key = api_key

        self.translator = Translator()
        self.iso_nationalities = [country.alpha_3 for country in pycountry.countries]
        print(f"\nInitialization time inside IDV Package: {time.time() - st}")

    def preprocess_image(self, image, sharpness=1.0, contrast=2.0,radius=2, percent=150, threshold=3):
        """Preprocess the image by sharpening and enhancing contrast."""

        # Apply sharpening
        enhancer = ImageEnhance.Sharpness(image)
        image = enhancer.enhance(sharpness)  # Sharpen the image (increase sharpness)

        # Enhance the contrast
        enhancer = ImageEnhance.Contrast(image)
        image = enhancer.enhance(contrast)  # Increase contrast

        image = image.filter(ImageFilter.UnsharpMask(radius=radius, percent=percent, threshold=threshold))

        return image


    def image_conversion(self,image):
        """
        This function decodes a base64 string data and returns an image object.
        If the image is in RGBA mode, it is converted to RGB mode.
        :return: an Image object that has been created from a base64 encoded string.
        """
        # Decode base64 String Data
        # img = Image.open(io.BytesIO(base64.decodebytes(bytes(image, "utf-8"))))
        
        img_data = io.BytesIO(base64.decodebytes(bytes(image, "utf-8")))
        with Image.open(img_data) as img:
            if img.mode == 'RGBA':
                # Create a blank background image
                background = Image.new('RGB', img.size, (255, 255, 255))
                # Paste the image on the background.
                background.paste(img, mask=img.split()[3])  # 3 is the alpha channel
                img = background
            else:
                img = img.copy()
            return img


    def rgb2yuv(self, img):
        """
        Convert an RGB image to YUV format.
        """
        try:
            img=np.array(img)
            return cv2.cvtColor(img, cv2.COLOR_RGB2YUV)
        except Exception as e:
            raise Exception(f"Error: {e}")
    
    def find_bright_areas(self, image, brightness_threshold):
        gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        thresh_image = cv2.threshold(gray_image, brightness_threshold, 255, cv2.THRESH_BINARY)[1]
        contours, hierarchy = cv2.findContours(thresh_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

        bright_areas = []

        for contour in contours:
            bounding_box = cv2.boundingRect(contour)

            area = bounding_box[2] * bounding_box[3]

            if area > 800:
                bright_areas.append(bounding_box)

        return len(bright_areas)

    def is_blurry(self, image):
        gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        
        laplacian_variance = cv2.Laplacian(gray_image, cv2.CV_64F).var()
        
        return laplacian_variance

    def identify_input_type(self, data):
        if isinstance(data, bytes):
                return "video_bytes"
        else:
            pass

        try:
            decoded_data = base64.b64decode(data)
            
            if decoded_data:
                return "base_64"
        except Exception:
            pass

        return "unknown"

    def sharpen_image(self, image):
        kernel = np.array([[-1, -1, -1],
                        [-1, 9, -1],
                        [-1, -1, -1]])
        return cv2.filter2D(image, -1, kernel)


    def adjust_contrast(self, image, factor):
        pil_image = Image.fromarray(image)
        enhancer = ImageEnhance.Contrast(pil_image)
        enhanced_image = enhancer.enhance(factor)
        return np.array(enhanced_image)

    def adjust_brightness(self, image, factor):
        pil_image = Image.fromarray(image)
        enhancer = ImageEnhance.Brightness(pil_image)
        enhanced_image = enhancer.enhance(factor)
        return np.array(enhanced_image)

    def enhance_quality(self, image):
        sharpened_image = self.sharpen_image(image)
        enhanced_image = self.adjust_brightness(sharpened_image, 1.2)
        enhanced_contrast = self.adjust_contrast(enhanced_image, 1.2)
        # grayscale_image = cv2.cvtColor(enhanced_contrast, cv2.COLOR_BGR2GRAY)
        
        return enhanced_contrast

    
    def check_document_quality(self,data):
        video_quality = {"error": ""}
        temp_video_file = tempfile.NamedTemporaryFile(delete=False)
        temp_video_file_path = temp_video_file.name
        
        try:
                # Write video bytes to the temporary file and flush
                temp_video_file.write(data)
                temp_video_file.flush()
                temp_video_file.close()  # Close the file to ensure it can be accessed by other processes

                video_capture = cv2.VideoCapture(temp_video_file.name)

                if video_capture.isOpened():
                    frame_count = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))

                    for _ in range(frame_count):
                        ret, frame = video_capture.read()
                        #                         if ret:
                        # frame_count_vid+=1
                        # if frame_count_vid % 10 == 0:
                        _, buffer = cv2.imencode(".jpg", frame)
                        image_data = buffer.tobytes()

                        image = vision_v1.Image(content=image_data)

                        response = self.client.face_detection(image=image)
                        if len(response.face_annotations) >= 1:
                            break

                video_capture.set(cv2.CAP_PROP_POS_FRAMES, 0)
            
                selfie_result = self.extract_selfie_from_video(video_capture)
                if isinstance(selfie_result, dict):
                    video_quality["error"] = selfie_result["error"]
                else:
                    (
                        selfie_blurry_result,
                        selfie_bright_result,
                    ) = self.get_blurred_and_glared_for_doc(selfie_result)
                    if (
                        selfie_blurry_result == "consider"
                        or selfie_bright_result == "consider"
                    ):
                        video_quality["error"] = "face_not_clear_in_video"
                    else:
                        video_quality["selfie"] = selfie_result
                        video_quality["shape"] = selfie_result.shape
           
                video_capture.release()  # Release the video capture

        # except Exception as e:
        #     video_quality["error"] = "bad_video"

        finally:
            # Ensure the temporary file is deleted
            if os.path.exists(temp_video_file_path):
                os.remove(temp_video_file_path)
                # print(f"Temporary file {temp_video_file_path} has been deleted.")
                
        return video_quality

    def extract_selfie_from_video(self, video_capture):
        """Extract the best quality selfie from video with speed optimizations for frontal faces."""
        video_dict = {'error': ''}

        try:
            total_frames = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
            if total_frames <= 0:
                video_dict['error'] = 'invalid_video_frame_count'
                return video_dict

            # Check only 6 frames - 2 at start, 2 in the middle, 2 at the end
            frame_positions = [
                int(total_frames * 0.05),
                int(total_frames * 0.15),
                int(total_frames * 0.45),
                int(total_frames * 0.55),
                int(total_frames * 0.85),
                int(total_frames * 0.95)
            ]

            best_face = None
            best_score = -1
            best_frame = None
            best_frame_position = None
            frame_results = []

            print(f"Analyzing video with {total_frames} frames")
            print(f"Checking {len(frame_positions)} strategic frames")

            for target_frame in frame_positions:
                if target_frame >= total_frames:
                    target_frame = total_frames - 1
                if target_frame < 0:
                    target_frame = 0

                video_capture.set(cv2.CAP_PROP_POS_FRAMES, target_frame)
                ret, frame = video_capture.read()
                if not ret or frame is None or frame.size == 0:
                    continue

                try:
                    scale = 0.7
                    small_frame = cv2.resize(frame, (0, 0), fx=scale, fy=scale)

                    encode_params = [cv2.IMWRITE_JPEG_QUALITY, 90]
                    _, buffer = cv2.imencode(".jpg", small_frame, encode_params)

                    image = vision_v1.Image(content=buffer.tobytes())
                    response = self.client.face_detection(image=image, max_results=2)
                    faces = response.face_annotations

                    if not faces:
                        continue

                    frame_best_face = None
                    frame_best_score = -1

                    for face in faces:
                        vertices = [(int(vertex.x / scale), int(vertex.y / scale))
                                    for vertex in face.bounding_poly.vertices]

                        left = min(v[0] for v in vertices)
                        upper = min(v[1] for v in vertices)
                        right = max(v[0] for v in vertices)
                        lower = max(v[1] for v in vertices)

                        # Validate face coordinates
                        if not (0 <= left < right <= frame.shape[1] and 0 <= upper < lower <= frame.shape[0]):
                            continue

                        # Calculate face metrics
                        face_width = right - left
                        face_height = lower - upper
                        face_area = (face_width * face_height) / (frame.shape[0] * frame.shape[1])

                        # Reject small faces
                        if face_area < 0.05:
                            continue

                        # Calculate how centered the face is
                        face_center_x = (left + right) / 2
                        face_center_y = (upper + lower) / 2
                        frame_center_x = frame.shape[1] / 2
                        frame_center_y = frame.shape[0] / 2

                        center_dist_x = abs(face_center_x - frame_center_x) / (frame.shape[1] / 2)
                        center_dist_y = abs(face_center_y - frame_center_y) / (frame.shape[0] / 2)
                        center_score = 1.0 - (center_dist_x + center_dist_y) / 2

                        # For frontal faces, left and right eye/ear should be roughly symmetric
                        if len(face.landmarks) > 0:
                            # Head rotation detection
                            roll, pan, tilt = 0, 0, 0
                            if hasattr(face, 'roll_angle'):
                                roll = abs(face.roll_angle)
                            if hasattr(face, 'pan_angle'):
                                pan = abs(face.pan_angle)
                            if hasattr(face, 'tilt_angle'):
                                tilt = abs(face.tilt_angle)

                            head_angle_penalty = (roll + pan + tilt) / 180.0

                            # Symmetry detection from face bounding box
                            left_half = face_center_x - left
                            right_half = right - face_center_x
                            width_ratio = min(left_half, right_half) / max(left_half, right_half)

                            # Frontal-face score: higher for more frontal faces
                            # Perfect frontal face would be 1.0
                            frontal_score = width_ratio * (1.0 - head_angle_penalty)
                        else:
                            # No landmarks, estimate from bounding box
                            left_half = face_center_x - left
                            right_half = right - face_center_x
                            frontal_score = min(left_half, right_half) / max(left_half, right_half)

                        # Combined score weights different factors
                        # More weight for frontal-ness and face confidence
                        score = (
                                face.detection_confidence * 0.3 +
                                face_area * 0.2 +
                                center_score * 0.2 +
                                frontal_score * 0.3
                        )

                        # Heavy bonus for very frontal faces (nearly symmetric)
                        if frontal_score > 0.8:
                            score *= 1.5

                        # Extra bonus for centered faces
                        if center_score > 0.8:
                            score *= 1.2

                        if score > frame_best_score:
                            # Add margins for the face
                            margin_x = int(face_width * 0.2)
                            margin_y_top = int(face_height * 0.3)
                            margin_y_bottom = int(face_height * 0.1)

                            left_with_margin = max(0, left - margin_x)
                            upper_with_margin = max(0, upper - margin_y_top)
                            right_with_margin = min(frame.shape[1], right + margin_x)
                            lower_with_margin = min(frame.shape[0], lower + margin_y_bottom)

                            # Store the best face info
                            frame_best_score = score
                            frame_best_face = {
                                'face': face,
                                'left': left_with_margin,
                                'upper': upper_with_margin,
                                'right': right_with_margin,
                                'lower': lower_with_margin,
                                'frontal_score': frontal_score,
                                'center_score': center_score,
                                'confidence': face.detection_confidence,
                                'frame': target_frame
                            }

                    if frame_best_face is not None:
                        frame_results.append({
                            'frame': target_frame,
                            'face': frame_best_face,
                            'score': frame_best_score,
                            'frame_data': frame.copy()
                        })

                        if frame_best_score > best_score:
                            best_score = frame_best_score
                            best_face = frame_best_face
                            best_frame = frame.copy()
                            best_frame_position = target_frame

                except Exception as e:
                    continue

            # Process results
            if len(frame_results) > 0:
                # Sort faces by score
                frame_results.sort(key=lambda x: x['score'], reverse=True)

                for i, result in enumerate(frame_results[:min(3, len(frame_results))]):
                    face_info = result['face']
                    print(f"Rank {i + 1}: Frame {face_info['frame']}, "
                          f"Score: {result['score']:.2f}, "
                          f"Frontal: {face_info['frontal_score']:.2f}, "
                          f"Center: {face_info['center_score']:.2f}")

                # Use the best frame
                best_result = frame_results[0]
                best_face = best_result['face']
                best_frame = best_result['frame_data']

                print(f"Selected frame {best_face['frame']} as best selfie")

            if best_face and best_frame is not None:
                try:
                    left = best_face['left']
                    upper = best_face['upper']
                    right = best_face['right']
                    lower = best_face['lower']

                    # Convert to RGB and crop
                    rgb_frame = cv2.cvtColor(best_frame, cv2.COLOR_BGR2RGB)
                    cropped_face = rgb_frame[upper:lower, left:right]

                    # Validate cropped face
                    if cropped_face is None or cropped_face.size == 0:
                        video_dict['error'] = 'invalid_cropped_face'
                        return video_dict

                    print(f"Face shape: {cropped_face.shape}")
                    return cropped_face

                except Exception as e:
                    video_dict['error'] = 'error_processing_detected_face'
                    return video_dict
            else:
                video_dict['error'] = 'no_suitable_face_detected_in_video'
                return video_dict

        except Exception as e:
            video_dict['error'] = 'video_processing_error'
            return video_dict

    def is_colored(self, base64_image):
        img = self.image_conversion(base64_image)
        img = np.array(img)

        return len(img.shape) == 3 and img.shape[2] >= 3
    
    def get_blurred_and_glared_for_doc(self, image, brightness_threshold=BRIGHTNESS_THRESHOLD, blur_threshold=BLUR_THRESHOLD):
        blurred = 'clear'
        glare = 'clear'
        
        blurry1 = self.is_blurry(image)
        if blurry1 < blur_threshold:
            blurred = 'consider'
        
        brightness1 = np.average(image[..., 0])
        if brightness1 > brightness_threshold:
            glare = 'consider'
        
        return blurred, glare

    def standardize_date(self, input_date):
        input_formats = [
            "%Y/%m/%d", "%m/%d/%Y", "%m-%d-%Y",
            "%Y-%m-%d", "%d-%m-%Y", "%d/%m/%Y",
            "%Y%m%d", "%m%d%Y", "%d%m%Y",
            "%Y.%m.%d", "%d.%m.%Y", "%m.%d.%Y",
            "%Y %m %d", "%d %m %Y", "%m %d %Y",
        ]

        for format in input_formats:
            try:
                parsed_date = datetime.strptime(input_date, format)
                standardized_date = parsed_date.strftime("%d/%m/%Y")
                return standardized_date
            except ValueError:
                pass

        return None

    def compare_dates(self, date_str1, date_str2):
        date_format = "%d/%m/%Y"

        date1 = datetime.strptime(date_str1, date_format)
        date2 = datetime.strptime(date_str2, date_format)

        if date1 == date2:
            return True
        else:
            return False

    # def validate_fields_id(self, complete_ocr_data, country):
    #     validation_flag = True

    #     if country == 'IRQ':
    #         front_id_number = complete_ocr_data.get('id_number_front', '')
    #         back_id_number = complete_ocr_data.get('id_number', '')

    #         front_card_number = complete_ocr_data.get('card_number_front', '')
    #         back_card_number = complete_ocr_data.get('card_number', '')
            
    #         name_front_id = complete_ocr_data.get('name_en', '')
            
    #         # print(f"NAME: {name_front_id}")

    #         if name_front_id:
    #             r = name_front_id.split(' ')
    #             if len(r)>=3:
    #                 name_front_id = r[0] + ' ' + r[-1]

    #         complete_name_back = f"{complete_ocr_data.get('first_name_back')} {complete_ocr_data.get('last_name_back')}"

    #         dob_generic = self.standardize_date(complete_ocr_data.get('dob'))
    #         if complete_ocr_data.get('dob_mrz'):
    #             dob_mrz = self.standardize_date(complete_ocr_data.get('dob_mrz'))
    #         else:
    #             dob_mrz = ''

    #         doe_generic = self.standardize_date(complete_ocr_data.get('expiry_date'))
    #         if complete_ocr_data.get('expiry_date_mrz'):
    #             doe_mrz = self.standardize_date(complete_ocr_data.get('expiry_date_mrz'))
    #         else:
    #             doe_generic = ''

    #         if front_id_number and back_id_number:
    #             if front_id_number != back_id_number:
    #                 print('ID number mismatch')
    #                 validation_flag = False

    #         if front_card_number and back_card_number:
    #             if front_card_number != back_card_number:
    #                 print('Card number mismatch')
    #                 validation_flag = False

    #         if complete_name_back and name_front_id:
    #             similarity = fuzz.partial_ratio(complete_name_back, name_front_id)
    #             print(similarity)
    #             if similarity < 50:
    #                 print('Name mismatch')
    #                 validation_flag = False

    #         if dob_generic and dob_mrz:
    #             if self.compare_dates(dob_generic, dob_mrz)==False:
    #                 print('DOB mismatch')
    #                 validation_flag = False
            
    #         if doe_generic and doe_mrz:
    #             if self.compare_dates(doe_generic, doe_mrz)==False:
    #                 print('DOE mismatch')
    #                 validation_flag = False
            
    #     return validation_flag

    def check_nationality_in_iso_list(self, nationality):
        if len(nationality) > 3:
            try:
                country = pycountry.countries.lookup(nationality)
                nationality =  country.alpha_3
            except:
                return 'consider'

        ## Handling case for OMN as it comes as MN, due to O being considered as 0
        if nationality.upper() == 'MN':
            nationality = 'OMN'

        if nationality.upper() in self.iso_nationalities:
            return 'clear'
        else:
            return 'consider'

    def get_face_orientation(self, face_landmarks):
        left_eye = np.array(face_landmarks['left_eye']).mean(axis=0)
        right_eye = np.array(face_landmarks['right_eye']).mean(axis=0)

        eye_slope = (right_eye[1] - left_eye[1]) / (right_eye[0] - left_eye[0])
        angle = np.degrees(np.arctan(eye_slope))

        return angle

    def rotate_image(self, img):
        from skimage.transform import radon
        
        img_array = np.array(img)

        if len(img_array.shape) == 2:
            gray = img_array
        else:
            gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)

        h, w = gray.shape
        if w > 640:
            gray = cv2.resize(gray, (640, int((h / w) * 640)))
        gray = gray - np.mean(gray)
        sinogram = radon(gray)
        r = np.array([np.sqrt(np.mean(np.abs(line) ** 2)) for line in sinogram.transpose()])
        rotation = np.argmax(r)
        angle = round(abs(90 - rotation) + 0.5)

        if abs(angle) > 5:
            rotated_img = img.rotate(angle, expand=True)
            return rotated_img

        return img

    def load_and_process_image_fr(self, base64_image, arr=False):
        try:
            if not arr:
                img = self.image_conversion(base64_image)
                img = np.array(img)
                image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            else:
                if base64_image.dtype != np.uint8:
                    base64_image = base64_image.astype(np.uint8)

                image = cv2.cvtColor(base64_image, cv2.COLOR_BGR2RGB)

            # base64_image = base64_image.split(',')[-1]
            # image_data = base64.b64decode(base64_image)
            # image_file = io.BytesIO(image_data)

            # image = face_recognition.load_image_file(image_file)

            face_locations = []
            face_locations = face_recognition.face_locations(image)

            if not face_locations:
                return [], []

            face_encodings = []
            face_encodings = face_recognition.face_encodings(image, face_locations)

            return face_locations, face_encodings
        except:
            return [], []

    def calculate_similarity(self, face_encoding1, face_encoding2):
        similarity_score = 1 - face_recognition.face_distance([face_encoding1], face_encoding2)[0]
        return round(similarity_score + 0.25, 2)

    def try_detect_face_at_angles(self, image_array):
        """Try detecting faces at different angles and return the first successful detection"""
        angles = [0, 90, 180, 270]
        best_result = ([], [])
        
        for angle in angles:
            if angle == 0:
                rotated = image_array
            else:
                rotated = np.array(Image.fromarray(image_array).rotate(angle, expand=True))
            
            face_locs, face_encs = self.load_and_process_image_fr(rotated, arr=True)
            if face_locs:
                return face_locs, face_encs, angle
        
        return [], [], None

    def try_detect_face_parallel(self, image_array):
        """Try detecting faces at different angles in parallel"""
        def check_angle(angle):
            try:
                # Create a copy of the image array to prevent memory issues
                image_copy = np.copy(image_array)
                
                if angle == 0:
                    rotated = image_copy
                else:
                    # Convert to PIL, rotate, and convert back to numpy
                    pil_image = Image.fromarray(image_copy)
                    rotated_pil = pil_image.rotate(angle, expand=True)
                    rotated = np.array(rotated_pil)
                
                # Ensure the array is contiguous
                if not rotated.flags['C_CONTIGUOUS']:
                    rotated = np.ascontiguousarray(rotated)
                
                face_locs, face_encs = self.load_and_process_image_fr(rotated, arr=True)
                return (face_locs, face_encs, angle)
            except Exception as e:
                print(f"Error processing angle {angle}: {e}")
                return ([], [], None)

        angles = [0, 90, 180, 270]
        results = []
        
        try:
            with ThreadPoolExecutor(max_workers=4) as executor:
                futures = [executor.submit(check_angle, angle) for angle in angles]
                
                for future in as_completed(futures):
                    try:
                        face_locs, face_encs, angle = future.result()
                        if face_locs:
                            # Cancel remaining futures
                            for f in futures:
                                f.cancel()
                            return face_locs, face_encs, angle
                        results.append((face_locs, face_encs, angle))
                    except Exception as e:
                        print(f"Error getting future result: {e}")
                        continue
        except Exception as e:
            print(f"Error in parallel processing: {e}")
        
        return [], [], None

    def try_detect_face_deepface(self, image_array):
        """Try detecting faces using DeepFace"""
        try:
            from idvpackage.common import load_and_process_image_deepface
            
            # Ensure image is in correct format
            if image_array is None:
                print("Image array is None")
                return [], [], None
            
            # Ensure we're working with RGB
            if len(image_array.shape) == 3 and image_array.shape[2] == 3:
                # If image is BGR, convert to RGB
                if image_array.dtype != np.uint8:
                    image_array = image_array.astype(np.uint8)
                # No need to convert color space as the image should already be in RGB
                face_locs, face_encs = load_and_process_image_deepface(image_array)
                return face_locs, face_encs, None
            else:
                print(f"Unexpected image shape: {image_array.shape}")
                return [], [], None
        except Exception as e:
            print(f"Error in try_detect_face_deepface: {e}")
            return [], [], None

    def extract_face_and_compute_similarity(self, selfie, front_face_locations, front_face_encodings):
        from idvpackage.common import load_and_process_image_deepface
        try:
            if selfie is None:
                print("Error: Selfie image is None")
                return 0
            
            # Ensure the input array is contiguous and in the correct format
            if not selfie.flags['C_CONTIGUOUS']:
                selfie = np.ascontiguousarray(selfie)
            
            # Convert array to uint8 if needed
            if selfie.dtype != np.uint8:
                if selfie.max() > 255:
                    selfie = (selfie / 256).astype(np.uint8)
                else:
                    selfie = selfie.astype(np.uint8)

            # Try DeepFace first as it's generally more reliable
            # start_time = time.time()
            face_locations1, face_encodings1 = load_and_process_image_deepface(selfie)
            # end_time = time.time()
            
            if not face_locations1 or not face_encodings1:
                print("No face detected in Selfie Video by DeepFace")
                return 0

            # print(f"Face detection took {end_time - start_time:.3f} seconds")
            
            face_locations2, face_encodings2 = front_face_locations, front_face_encodings

            if not face_encodings2.any():
                print('No face detected in front ID')
                return 0
            
            largest_face_index1 = face_locations1.index(max(face_locations1, key=lambda loc: (loc[2] - loc[0]) * (loc[3] - loc[1])))
            largest_face_index2 = face_locations2.index(max(face_locations2, key=lambda loc: (loc[2] - loc[0]) * (loc[3] - loc[1])))

            face_encoding1 = face_encodings1[largest_face_index1]
            face_encoding2 = face_encodings2[largest_face_index2]

            similarity_score = self.calculate_similarity(face_encoding1, face_encoding2)
            # print(f"Calculated similarity score: {similarity_score}")

            return min(1, similarity_score)
        
        except Exception as e:
            print(f"Error in extract_face_and_compute_similarity: {e}")
            import traceback
            traceback.print_exc()
            return 0

    def calculate_landmarks_movement(self, current_landmarks, previous_landmarks):
        return sum(
            abs(cur_point.position.x - prev_point.position.x) +
            abs(cur_point.position.y - prev_point.position.y)
            for cur_point, prev_point in zip(current_landmarks, previous_landmarks)
        )

    def calculate_face_movement(self, current_face, previous_face):
        return abs(current_face[0].x - previous_face[0].x) + abs(current_face[0].y - previous_face[0].y)

    def calculate_liveness_result(self, eyebrow_movement, nose_movement, lip_movement, face_movement):
        eyebrow_movement_threshold = 15.0
        nose_movement_threshold = 15.0
        lip_movement_threshold = 15.0
        face_movement_threshold = 10.0
        
        if (
            eyebrow_movement > eyebrow_movement_threshold or
            nose_movement > nose_movement_threshold or
            lip_movement > lip_movement_threshold or
            face_movement > face_movement_threshold
        ):
            return True
        else:
            return False

    def detect_image_format(self, base64_image):
        import imghdr

        decoded_image = base64.b64decode(base64_image)
        format = imghdr.what(None, decoded_image)
        
        return format

    
    def frame_count_and_save(self,cap):
            frames = []
            status, frame = cap.read()
            while status:
                frames.append(frame)
                status, frame = cap.read()

            cap.release()
            return frames

    def check_for_liveness(  self, similarity, video_bytes, face_match_threshold=0.59):
        st = time.time()
        from idvpackage.liveness_spoofing_v2 import test
        # Create a temporary file that will not be deleted automatically
        temp_video_file = tempfile.NamedTemporaryFile(delete=False)
        temp_video_file_path = temp_video_file.name
        
        try:
            # Write video bytes to the temporary file and flush
            temp_video_file.write(video_bytes)
            temp_video_file.flush()
            temp_video_file.close()  # Close the file to ensure it can be accessed by other processes
            
            try:
                result = test(temp_video_file_path)
                if result:
                    return result
            except Exception as e:
                print(f'\nError in Liveness: {e}')
                # print("Unable to open video file.")
                return None
            
            # Open the video file with OpenCV
            # cap = cv2.VideoCapture(temp_video_file_path)
            # if not cap.isOpened():
            #     # print("Unable to open video file.")
            #     return None

            # liveness_result_list = []
            # frames =   self.frame_count_and_save(cap)
            # frame_count = len(frames)
            # # print(f"FRAMES: {frame_count}")

            # frames_to_process = [frames[0], frames[-1]] if frame_count > 1 else [frames[0]]
            # previous_landmarks = None
            # previous_face = None

            # for frame in frames_to_process:
            #     _, buffer = cv2.imencode('.jpg', frame)
            #     image_data = buffer.tobytes()
            #     image = vision_v1.Image(content=image_data)
            #     response =   self.client.face_detection(image=image)
            #     faces = response.face_annotations

            #     if not faces:
            #         print("No faces detected in the frame")
            #         continue

            #     largest_face = None
            #     largest_face_area = 0

            #     for face in faces:
            #         current_landmarks = face.landmarks
            #         current_face = face.bounding_poly.vertices
            #         face_area = abs((current_face[2].x - current_face[0].x) * (current_face[2].y - current_face[0].y))

            #         if face_area > largest_face_area:
            #             largest_face = face
            #             largest_face_area = face_area

            #     if largest_face:
            #         current_landmarks = largest_face.landmarks
            #         current_face = largest_face.bounding_poly.vertices

            #         if previous_landmarks and previous_face:
            #             eyebrow_movement =   self.calculate_landmarks_movement(current_landmarks[:10], previous_landmarks[:10])
            #             nose_movement =   self.calculate_landmarks_movement(current_landmarks[10:20], previous_landmarks[10:20])
            #             lip_movement =   self.calculate_landmarks_movement(current_landmarks[20:28], previous_landmarks[20:28])
            #             face_movement =  self. calculate_face_movement(current_face, previous_face)

            #             liveness_result =   self.calculate_liveness_result(eyebrow_movement, nose_movement, lip_movement, face_movement)
            #             liveness_result_list.append(liveness_result)

            #         previous_landmarks = current_landmarks
            #         previous_face = current_face

            # cap.release()  # Release the video capture

            # liveness_check_result = 'clear' if any(liveness_result_list) else 'consider'

            # return liveness_check_result

        finally:
            # Ensure the temporary file is deleted
            if os.path.exists(temp_video_file_path):
                os.remove(temp_video_file_path)
                # print(f"Temporary file {temp_video_file_path} has been deleted.")
            logging.info(f"--------------Time taken for Liveness and Spoofing in IDV package: {time.time() - st} seconds\n")


    def convert_dob(self, input_date):
        day = input_date[4:6]
        month = input_date[2:4]
        year = input_date[0:2]

        current_year = datetime.now().year
        current_century = current_year // 100
        current_year_last_two_digits = current_year % 100

        century = current_century
        # If the given year is greater than the last two digits of the current year, assume last century
        if int(year) > current_year_last_two_digits:
            century = current_century - 1

        final_date = f"{day}/{month}/{century}{year}"

        return final_date

    def convert_expiry_date(self, input_date):
        day = input_date[4:6]
        month = input_date[2:4]
        year = input_date[0:2]

        current_year = datetime.now().year
        current_century = current_year // 100
        current_year_last_two_digits = current_year % 100
        century = current_century

        if int(year) <= current_year_last_two_digits:
            century = current_century
        else:
            century = current_century
        final_date = f"{day}/{month}/{century}{year}"

        return final_date

    def clean_string(self, input_string):
        cleaned_string = re.sub(r'[^\w\s]', ' ', input_string)
        return cleaned_string.strip()
    

    def find_and_slice_number(input_number, digits):
            from itertools import permutations
            # Generate all possible permutations of the digits
            perms = [''.join(p) for p in permutations(digits)]
            
            # Initialize variables to keep track of the found pattern and its index
            found_pattern = None
            found_index = -1

            # Search for any permutation of the digits in the input_number
            for perm in perms:
                found_index = input_number.find(perm)
                if found_index != -1:
                    found_pattern = perm
                    break

            # If a pattern is found, slice the number accordingly
            if found_pattern:
                if found_index > len(input_number) - found_index - len(found_pattern):
                    # Slice to the left
                    sliced_number = input_number[:found_index + len(found_pattern)]
                else:
                    # Slice to the right
                    sliced_number = input_number[found_index:]
                
                return sliced_number
            else:
                return ''
            
    def get_ocr_results(self, processed_image, country=None, side=None):
        # with io.BytesIO() as output:
        #     processed_image.save(output, format="PNG")
        #     image_data = output.getvalue()

        # image = vision_v1.types.Image(content=image_data)

        if country == 'QAT' or country == 'LBN' or country == 'IRQ' or country == 'SDN':
            image = vision_v1.types.Image(content=processed_image)
        
        else:
            compressed_image = BytesIO()
            processed_image.save(compressed_image, format="JPEG", quality=60, optimize=True)
            compressed_image_data = compressed_image.getvalue()
            image = vision_v1.types.Image(content=compressed_image_data)

        response = self.client.text_detection(image=image)
        id_infos = response.text_annotations

        return id_infos

    def extract_document_info(self, image, side, document_type, country):
        st = time.time()
        document_data = {}
        if side=='front':
            document_data = self.extract_front_id_info(image, country)
            logging.info(f"--------------Time taken for Front ID Extraction in IDV package: {time.time() - st} seconds\n")
            return document_data

        if side=='back':
            document_data = self.extract_back_id_info(image, country)
            logging.info(f"--------------Time taken for Back ID Extraction in IDV package: {time.time() - st} seconds\n")

        elif document_type == 'passport':
            document_data = self.exract_passport_info(image, country)
            logging.info(f"--------------Time taken for Passport Extraction in IDV package: {time.time() - st} seconds\n")
        
        elif document_type == 'driving_license':
            pass
        
        return document_data
        
    def extract_front_id_info(self, front_id, country):
        if country == 'UAE':
                print("working on UAE")
                from idvpackage.ocr_utils import detect_logo, detect_photo_on_screen, detect_screenshot, document_on_printed_paper
                from idvpackage.common import detect_id_card_uae, load_and_process_image_deepface

                front_data = {
                    'error': '',
                    'doc_type': 'national_identity_card'
                }

                try:
                    processed_front_id = self.image_conversion(front_id)
                    front_id_text = self.get_ocr_results(processed_front_id)
                    front_id_text_desc = front_id_text[0].description

                    combined_pattern = r'(Resident Identity|Identity Card|Golden Card|FEDERAL AUTHORITY FOR IDENTITY)'
                    match = re.search(combined_pattern, front_id_text_desc, re.IGNORECASE)

                    if not match:
                        front_data["error"] = "not_front_id"
                        return front_data
                    
                    img = processed_front_id
                    image = np.array(img)
                    pil_image = Image.fromarray(image)

                    doc_on_pp_result = document_on_printed_paper(image)

                    with io.BytesIO() as output:
                        pil_image.save(output, format="PNG")
                        image_data = output.getvalue()

                    # logo_result = detect_logo(self.client, image_data, country)
                    logo_result = 'clear'

                    screenshot_result = detect_screenshot(self.client, front_id)
                    photo_on_screen_result = detect_photo_on_screen(self.client, front_id)

                    front_blurred, front_glare = self.get_blurred_and_glared_for_doc(image)
                    # print(f"blurred, glare: {front_blurred, front_glare}")

                    front_face_locations, front_face_encodings = load_and_process_image_deepface(front_id)
                    # front_face_locations, front_face_encodings = self.load_and_process_image_fr(front_id)

                    front_face_locations_str = json.dumps([tuple(face_loc) for face_loc in front_face_locations])
                    front_face_encodings_str = json.dumps([face_enc.tolist() for face_enc in front_face_encodings])

                    tampered_result, part_text = detect_id_card_uae(self.client, image_data, front_id_text)

                    # dob, expiry = '', ''
                    # date_matches = re.findall(r'\d{2}/\d{2}/\d{4}', front_id_text_desc)
                    # sorted_dates = sorted(date_matches)
                    
                    # if len(sorted_dates) > 1:
                    #     dob = sorted_dates[0]
                    #     expiry = sorted_dates[-1]

                    front_data = {
                        'front_extracted_data': front_id_text_desc,
                        'front_coloured': True,
                        'front_doc_on_pp': doc_on_pp_result,
                        'front_logo_result': logo_result,
                        'front_screenshot_result': screenshot_result,
                        'front_photo_on_screen_result': photo_on_screen_result,
                        'front_blurred': front_blurred, 
                        'front_glare': front_glare,
                        'front_face_locations': front_face_locations_str, 
                        'front_face_encodings': front_face_encodings_str,
                        'front_tampered_result': tampered_result
                    }

                    non_optional_keys = ["front_face_locations", "front_face_encodings"]
                    empty_string_keys = [key for key, value in front_data.items() if key in non_optional_keys and value == '']

                    if empty_string_keys:
                        front_data['error'] = 'covered_photo'

                except Exception as e:
                    front_data['error'] = 'bad_image'
                    front_data['error_details'] = e
                

            
                return front_data

        if country == 'SAU':
                from idvpackage.ocr_utils import detect_photo_on_screen, detect_screenshot, document_on_printed_paper
                from idvpackage.sau_id_extraction import extract_id_details
                front_data = {
                    'error': '',
                    'doc_type': 'national_identity_card'
                }

                try:
                    processed_front_id = self.image_conversion(front_id)
                    front_id_text = self.get_ocr_results(processed_front_id)
                    front_id_text = front_id_text[0].description
                    front_id_text_list= front_id_text.split('\n')

                    img = self.image_conversion(front_id)
                    image = np.array(img)
                    pil_image = Image.fromarray(image)

                    doc_on_pp_result = document_on_printed_paper(image)

                    with io.BytesIO() as output:
                        pil_image.save(output, format="PNG")
                        image_data = output.getvalue()
                    logo_result = 'clear'
                    screenshot_result = detect_screenshot(self.client, front_id)
                    photo_on_screen_result = detect_photo_on_screen(self.client, front_id)

                    front_blurred, front_glare = self.get_blurred_and_glared_for_doc(image)

                    front_face_locations, front_face_encodings = self.load_and_process_image_fr(front_id)

                    front_face_locations_str = json.dumps([tuple(face_loc) for face_loc in front_face_locations])
                    front_face_encodings_str = json.dumps([face_enc.tolist() for face_enc in front_face_encodings])

                    front_data_fields = extract_id_details(front_id_text_list)
                    valid_nationality_result = self.check_nationality_in_iso_list(front_data_fields.get('nationality'))

                    front_data = {
                        'valid_nationality': valid_nationality_result,
                        'front_extracted_data': front_id_text,
                        'front_coloured': True,
                        'front_doc_on_pp': doc_on_pp_result,
                        'front_logo_result': logo_result,
                        'front_screenshot_result': screenshot_result,
                        'front_photo_on_screen_result': photo_on_screen_result,
                        'front_blurred': front_blurred, 
                        'front_glare': front_glare,
                        'front_face_locations': front_face_locations_str, 
                        'front_face_encodings': front_face_encodings_str
                    }
                    
                    front_data.update(front_data_fields)

                    non_optional_keys = ["front_face_locations", "front_face_encodings", "id_number", "name", "dob", "expiry_date", "gender", "nationality"]
                    empty_string_keys = [key for key, value in front_data.items() if key in non_optional_keys and value == '']

                    if empty_string_keys:
                        front_data['error'] = 'covered_photo'

                except Exception as e:
                    front_data['error'] = 'bad_image'
                    front_data['error_details'] = e
                

            
                return front_data
        
        if country == 'IRQ':
                print("working on IRQ")
                from idvpackage.ocr_utils import  detect_photo_on_screen, detect_screenshot, document_on_printed_paper
                from idvpackage.iraq_id_extraction import iraq_front_id_extraction
                from idvpackage.common import load_and_process_image_deepface

                front_data = {
                    'error': '',
                    'doc_type': 'national_identity_card'
                }

                try:
                    st = time.time()
                    processed_front_id = self.image_conversion(front_id)
                    logging.info(f'----------------Time taken for image conversion front: {time.time() - st} seconds\n')

                    st = time.time()
                    compressed_image = BytesIO()
                    processed_front_id.save(compressed_image, format="JPEG", quality=60, optimize=True)
                    compressed_image_data = compressed_image.getvalue()
                    
                    front_id_text = self.get_ocr_results(compressed_image_data, country = 'IRQ', side = 'front')
                    front_id_text_desc = front_id_text[0].description
                    logging.info(f'----------------Time taken for vision front: {time.time() - st} seconds\n')

                    try:
                        translated_id_text = self.translator.translate(front_id_text_desc, src='ar', dest='en').text
                    except Exception as e:
                        logging.info(f'--------------Fallback for translation keyword matching front\n')
                        from deep_translator import GoogleTranslator
                        translated_id_text = GoogleTranslator('ar', 'en').translate(front_id_text_desc)

                    # logging.info(f'\n----------------Time taken for translation: {time.time() - st} seconds')

                    combined_pattern = r'(Ministry of Interior|Republic of Iraq|National Card|Passports and Residence|Republic|Ministry|Iraq)'
                    match = re.search(combined_pattern, translated_id_text, re.IGNORECASE)

                    if not match:
                        front_data["error"] = "not_front_id"
                        return front_data
                    
                    st = time.time()
                    image = np.array(processed_front_id)
                    # pil_image = Image.fromarray(image)

                    doc_on_pp_result = document_on_printed_paper(image)

                    # with io.BytesIO() as output:
                    #     pil_image.save(output, format="PNG")
                    #     image_data = output.getvalue()

                    template_result = 'clear'
                    logo_result = 'clear'

                    screenshot_result = detect_screenshot(self.client, front_id)
                    photo_on_screen_result = detect_photo_on_screen(self.client, front_id)
                
                    front_blurred, front_glare = self.get_blurred_and_glared_for_doc(image)
                    logging.info(f'----------------Time taken for fraud detection attributes front: {time.time() - st} seconds\n')

                    st = time.time()
                    front_face_locations, front_face_encodings = load_and_process_image_deepface(front_id)
                    logging.info(f'----------------Time taken for face extraction front: {time.time() - st} seconds\n')
                    # front_face_locations, front_face_encodings = self.load_and_process_image_fr(front_id)

                    front_face_locations_str = json.dumps([tuple(face_loc) for face_loc in front_face_locations])
                    front_face_encodings_str = json.dumps([face_enc.tolist() for face_enc in front_face_encodings])

                    # image_format = 'jpg'
                    st = time.time()
                    image_format = self.detect_image_format(front_id)

                    front_data_fields = iraq_front_id_extraction(self.client, compressed_image_data, front_id_text, front_id_text_desc, translated_id_text, image_format)
                    logging.info(f'----------------Time taken for iraq data formatting and final extraction front: {time.time() - st} seconds\n')

                    front_data = {
                        'front_extracted_data': front_id_text_desc,
                        'translated_front_id_text': translated_id_text,
                        'front_coloured': True,
                        'front_doc_on_pp': doc_on_pp_result,
                        'front_logo_result': logo_result,
                        'front_template_result': template_result,
                        # 'front_tampered_result': tampered_result_front,
                        'front_screenshot_result': screenshot_result,
                        'front_photo_on_screen_result': photo_on_screen_result,
                        'front_blurred': front_blurred, 
                        'front_glare': front_glare,
                        'front_face_locations': front_face_locations_str, 
                        'front_face_encodings': front_face_encodings_str
                    }
                    
                    front_data.update(front_data_fields)

                    required_keys = ["front_face_locations", "front_face_encodings", "id_number", "name"]
                    empty_string_keys = [key for key, value in front_data.items() if key in required_keys and value == '']

                    if empty_string_keys:
                        front_data['error'] = 'covered_photo'

                except Exception as e:
                    front_data['error'] = 'bad_image'
                    front_data['error_details'] = e

            
                return front_data

        if country == 'QAT':
                print("working on QAT with compression")
                from idvpackage.qatar_id_extraction import qatar_front_id_extraction
                from idvpackage.common import load_and_process_image_deepface_all_orientations

                front_data = {
                    'error': '',
                    'doc_type': 'national_identity_card'
                }

            
                try:
                    st = time.time()
                    processed_front_id = self.image_conversion(front_id)
                    logging.info(f'----------------Time taken for image conversion front: {time.time() - st} seconds\n')

                    st = time.time()
                    compressed_image = BytesIO()
                    processed_front_id.save(compressed_image, format="JPEG", quality=70, optimize=True)
                    compressed_image_data = compressed_image.getvalue()
                    
                    front_id_text = self.get_ocr_results(compressed_image_data, country = 'QAT', side = 'front')
                    front_id_text_desc = front_id_text[0].description
                    logging.info(f'----------------Time taken for vision front: {time.time() - st} seconds\n')
                    combined_pattern = r'(State of Qatar|Residency Permit)'
                    match = re.search(combined_pattern, front_id_text_desc, re.IGNORECASE)

                    if not match:
                        front_data["error"] = "not_front_id"
                        return front_data
                    
                    
                    # image = np.array(processed_front_id)
                    # doc_on_pp_result = document_on_printed_paper(image)

                    st = time.time()
                    front_face_locations, front_face_encodings = load_and_process_image_deepface_all_orientations(front_id)
                    # front_face_locations, front_face_encodings = load_and_process_image_deepface_optimized(front_id)
                    logging.info(f'----------------Time taken for face extraction front: {time.time() - st} seconds\n')
                    # front_face_locations, front_face_encodings = self.load_and_process_image_fr(front_id)
                    
                    front_face_locations_str = json.dumps([tuple(face_loc) for face_loc in front_face_locations])
                    front_face_encodings_str = json.dumps([face_enc.tolist() for face_enc in front_face_encodings])

                    st = time.time()
                    front_data_fields = qatar_front_id_extraction(self.client, compressed_image_data, front_id_text, front_id_text_desc)
                    logging.info(f'----------------Time taken for qatar data formatting and final extraction front: {time.time() - st} seconds\n')
                    
                    expiry_date = front_data_fields.get('expiry_date', '')

                    # print(f"TAMPERING: {tampered_result_front}")
                    # valid_nationality_result = self.check_nationality_in_iso_list(front_data_fields.get('nationality'))

                    front_data_temp = {
                        'front_extracted_data': front_id_text_desc,
                        'valid_nationality': 'clear',
                        'front_coloured': True,
                        'front_doc_on_pp': 'clear',
                        'front_logo_result': 'clear',
                        'front_template_result': 'clear',
                        # 'front_tampered_result': tampered_result_front,
                        'front_screenshot_result': 'clear',
                        'front_photo_on_screen_result': 'clear',
                        'front_blurred': 'clear', 
                        'front_glare': 'clear',
                        'front_face_locations': front_face_locations_str, 
                        'front_face_encodings': front_face_encodings_str
                    }
                    
                    front_data_fields.update(front_data_temp)
                    front_data.update(front_data_fields)

                    error = ""
                    if expiry_date:
                        try:
                            dt_obj = datetime.strptime(expiry_date, '%d/%m/%Y')
                            tomorrow = datetime.today() + timedelta(days=1)
                            if dt_obj < tomorrow:
                                front_data['error'] = 'expired_id'
                        except:
                            pass
                        
                    required_keys = ["expiry", "name", "id_number"]
                    empty_string_keys = [key for key, value in front_data.items() if key in required_keys and value == '']

                    if empty_string_keys:
                        front_data['error'] = 'covered_photo'


                except Exception as e:
                    print(e)
                    front_data['error'] = 'bad_image'
                    front_data['error_details'] = e
            
                return front_data
        
        if country == 'LBN':
                logging.info("----------------Working on LBN\n")
                from idvpackage.ocr_utils import detect_logo, detect_photo_on_screen, detect_screenshot, document_on_printed_paper
                from idvpackage.lebanon_id_extraction import lebanon_front_id_extraction
                from idvpackage.common import load_and_process_image_deepface
                from idvpackage.blur_detection import is_image_blur
                
                front_data = {
                    'error': '',
                    'doc_type': 'national_identity_card'
                }
          
                try:
                    st = time.time()
                    processed_front_id = self.image_conversion(front_id)
                    logging.info(f'----------------Time taken for image conversion front: {time.time() - st} seconds\n')
                    
                    st = time.time()
                    compressed_image = BytesIO()
                    processed_front_id.save(compressed_image, format="JPEG", quality=90, optimize=True)
                    compressed_image_data = compressed_image.getvalue()

                    front_id_text = self.get_ocr_results(compressed_image_data, country='LBN')
                    front_id_text_desc = front_id_text[0].description

                    logging.info(f'----------------Time taken for vision front: {time.time() - st} seconds\n')

                    st = time.time()
                    try:
                        translated_id_text = self.translator.translate(front_id_text_desc, src='ar', dest='en').text
                    except:
                        logging.info(f'\n--------------Fallback for translation keyword matching')
                        from deep_translator import GoogleTranslator
                        translated_id_text = GoogleTranslator('ar', 'en').translate(front_id_text_desc)
                    logging.info(f'----------------Time taken for ar-en translation front: {time.time() - st} seconds\n')

                    combined_pattern = r'(Lebanon Republic|Ministry of)'
                    match = re.search(combined_pattern, translated_id_text, re.IGNORECASE)

                    if not match:
                        front_data["error"] = "not_front_id"
                        return front_data
                    
                    # Check for Civil status document keywords in translated text
                    civil_status_keywords = r'(General Directorate|Convergence|Municipalities|Personal Status|statement of individuals)'
                    civil_status_match = re.search(civil_status_keywords, translated_id_text, re.IGNORECASE)
                    
                    if civil_status_match:
                        front_data["error"] = "not_front_id"
                        return front_data
                    
                    # Check for blur using the new comprehensive method
                    image = np.array(processed_front_id)
                    if is_image_blur(image, laplace_threshold=30, canny_threshold=1500, checks_threshold=120, min_area_percentage=1.0):
                        print(f"Blur/Brightness issue detected in front image, marking as covered photo")
                        front_data['error'] = 'blur_photo'
                        return front_data

                    # Extract data and check for necessary keys
                    st = time.time()
                    front_data_fields = lebanon_front_id_extraction(front_id_text_desc)
                    logging.info(f'----------------Time taken for data formatting and final extraction front: {time.time() - st} seconds\n')
                    
                    required_keys = ["name", "dob", "dob_en", "place_of_birth_ar", "id_number", "expiry_date"]

                    empty_string_keys = [
                        key for key, value in front_data_fields.items()
                        if key in required_keys and (value == '' or value == '[]' or value == [] or not value)
                    ]

                    if empty_string_keys:
                        front_data['error'] = 'covered_photo'

                    image = np.array(processed_front_id)
                    
                    st = time.time()
                    ## TODO: doc_on_pp and detect_photo_on_screen for LBN
                    # doc_on_pp_result = document_on_printed_paper(image)
                    doc_on_pp_result = 'clear'
                    ## no logo for Lebanon ID's
                    logo_result = 'clear'
                    template_result = 'clear'
                    ## TODO: template matching for Lebanon ID's
                    # template_result = detect_logo(self.client, compressed_image_data, country, compare_type='template', side='front')
                    ## TODO: tampering result for Lebanon ID's - pending tampered samples
                    # tampered_result_front = calculate_error_difference(np.array(Image.open(io.BytesIO(base64.decodebytes(bytes(front_id, "utf-8"))))))
                    screenshot_result = detect_screenshot(self.client, front_id)
                    # photo_on_screen_result = detect_photo_on_screen(self.client, image)
                    photo_on_screen_result = 'clear'
                    front_blurred, front_glare = self.get_blurred_and_glared_for_doc(image)
                    logging.info(f'----------------Time taken for fraud detection attributes front: {time.time() - st} seconds\n')

                    # front_face_locations, front_face_encodings = self.load_and_process_image_fr(front_id)
                    st = time.time()
                    front_face_locations, front_face_encodings = load_and_process_image_deepface(front_id)
                    logging.info(f'----------------Time taken for face extraction front: {time.time() - st} seconds\n')

                    front_face_locations_str = json.dumps([tuple(face_loc) for face_loc in front_face_locations])
                    front_face_encodings_str = json.dumps([face_enc.tolist() for face_enc in front_face_encodings])


                    # print(f"TAMPERING: {tampered_result_front}")

                    front_data_temp = {
                        'front_extracted_data': front_id_text_desc,
                        'translated_front_id_text': translated_id_text,
                        'front_coloured': True,
                        'front_doc_on_pp': doc_on_pp_result,
                        'front_logo_result': logo_result,
                        'front_template_result': template_result,
                        # 'front_tampered_result': tampered_result_front,
                        'front_screenshot_result': screenshot_result,
                        'front_photo_on_screen_result': photo_on_screen_result,
                        'front_blurred': front_blurred, 
                        'front_glare': front_glare,
                        'front_face_locations': front_face_locations_str, 
                        'front_face_encodings': front_face_encodings_str
                    }
                    
                    front_data.update(front_data_temp)
                    front_data.update(front_data_fields)

                    required_keys = ["front_face_locations", "front_face_encodings", "name", "dob", "dob_en", "place_of_birth_ar", "id_number", "expiry_date"]

                    empty_string_keys = [
                        key for key, value in front_data.items()
                        if key in required_keys and (value == '' or value == '[]' or value == [] or not value)
                    ]

                    if empty_string_keys:
                        front_data['error'] = 'covered_photo'

                except Exception as e:
                    front_data['error'] = 'bad_image'
                    front_data['error_details'] = e
                
            
                return front_data

        if country == 'SDN':
            logging.info("----------------Working on SDN\n")
            from idvpackage.ocr_utils import detect_logo, detect_photo_on_screen, detect_screenshot, \
                document_on_printed_paper
            from idvpackage.sudan_id_extraction import sdn_front_id_extraction
            from idvpackage.common import load_and_process_image_deepface
            from idvpackage.blur_detection import is_image_blur

            front_data = {
                'error': '',
                'doc_type': 'national_identity_card'
            }

            try:
                st = time.time()
                processed_front_id = self.image_conversion(front_id)
                logging.info(f'----------------Time taken for image conversion front: {time.time() - st} seconds\n')

                st = time.time()
                compressed_image = BytesIO()
                processed_front_id.save(compressed_image, format="JPEG", quality=90, optimize=True)
                compressed_image_data = compressed_image.getvalue()

                front_id_text = self.get_ocr_results(compressed_image_data, country='SDN')
                front_id_text_desc = front_id_text[0].description
                logging.info(f'----------------Time taken for vision front: {time.time() - st} seconds\n')

                st = time.time()
                try:
                    translated_id_text = self.translator.translate(front_id_text_desc, src='ar', dest='en').text
                except:
                    logging.info(f'\n--------------Fallback for translation keyword matching')
                    from deep_translator import GoogleTranslator
                    translated_id_text = GoogleTranslator('ar', 'en').translate(front_id_text_desc)
                logging.info(f'----------------Time taken for ar-en translation front: {time.time() - st} seconds\n')

                combined_pattern = r'(Republic of the Sudan|Republic of Sudan|ID Card|SDN)'
                match = re.search(combined_pattern, translated_id_text, re.IGNORECASE)

                if not match:
                    front_data["error"] = "not_front_id"
                    return front_data

                image = np.array(processed_front_id)

                # blur_test = is_image_blur(image)
                # if blur_test == True:
                #     print(f"Front ID Document is blurry, marking as covered photo")
                #     front_data['error'] = 'covered_photo'
                #     return front_data

                ## TODO: doc_on_pp for SDN
                # doc_on_pp_result = document_on_printed_paper(image)
                doc_on_pp_result = 'clear'
                ## no logo detection for SDN ID's
                logo_result = 'clear'
                template_result = 'clear'
                ## TODO: template matching for SDN ID's
                # template_result = detect_logo(self.client, compressed_image_data, country, compare_type='template', side='front')
                ## TODO: tampering result for SDN ID's - pending tampered samples
                # tampered_result_front = calculate_error_difference(np.array(Image.open(io.BytesIO(base64.decodebytes(bytes(front_id, "utf-8"))))))

                st = time.time()
                front_data_fields = sdn_front_id_extraction(self.client, front_id_text_desc, compressed_image_data,
                                                            front_id_text, compressed_image)
                logging.info(
                    f'----------------Time taken for data formatting and final extraction front: {time.time() - st} seconds\n')
                # Check age based on DOB
                try:
                    dob = front_data_fields.get('dob', '')
                    if dob:
                        try:
                            dob_date = datetime.strptime(dob, "%Y/%m/%d")
                        except:
                            try:
                                dob_date = datetime.strptime(dob, "%Y-%m-%d")
                            except:
                                try:
                                    dob_date = datetime.strptime(dob, "%d/%m/%Y")
                                except:
                                    dob_date = None

                        if dob_date:
                            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 < 18:
                                front_data['error'] = 'under_age'
                                return front_data
                except:
                    pass

                st = time.time()
                screenshot_result = detect_screenshot(self.client, front_id)
                # photo_on_screen_result = detect_photo_on_screen(self.client, image)
                photo_on_screen_result = 'clear'
                front_blurred, front_glare = self.get_blurred_and_glared_for_doc(image)
                logging.info(
                    f'----------------Time taken for fraud detection attributes front: {time.time() - st} seconds\n')

                # front_face_locations, front_face_encodings = self.load_and_process_image_fr(front_id)
                st = time.time()
                front_face_locations, front_face_encodings = load_and_process_image_deepface(front_id, country='SDN')
                logging.info(f'----------------Time taken for face extraction front: {time.time() - st} seconds\n')

                front_face_locations_str = json.dumps([tuple(face_loc) for face_loc in front_face_locations])
                front_face_encodings_str = json.dumps([face_enc.tolist() for face_enc in front_face_encodings])

                # print(f"TAMPERING: {tampered_result_front}")

                front_data_temp = {
                    'front_extracted_data': front_id_text_desc,
                    'translated_front_id_text': translated_id_text,
                    'front_coloured': True,
                    'front_doc_on_pp': doc_on_pp_result,
                    'front_logo_result': logo_result,
                    'front_template_result': template_result,
                    # 'front_tampered_result': tampered_result_front,
                    'front_screenshot_result': screenshot_result,
                    'front_photo_on_screen_result': photo_on_screen_result,
                    'front_blurred': front_blurred,
                    'front_glare': front_glare,
                    'front_face_locations': front_face_locations_str,
                    'front_face_encodings': front_face_encodings_str
                }

                front_data_fields.update(front_data_temp)
                front_data.update(front_data_fields)

                required_keys = ["front_face_locations", "id_number", "front_face_encodings", "full_name", "dob"]
                empty_string_keys = [key for key, value in front_data.items() if key in required_keys and value == '']

                if empty_string_keys:
                    front_data['error'] = 'missing_key_fields'


            except Exception as e:
                front_data['error'] = 'Something went wrong'
                print(f"-------------->> Something went wrong error trace:: {e}")
                front_data['error_details'] = e

            # try:
            #     list_1 = front_data['name_ar'].split(" ")
            #     filtered_names = [name for name in list_1 if len(name) > 1]
            #     front_data['first_name_ar'] = filtered_names[0]
            #     front_data['last_name_ar'] = filtered_names[3]
            #     front_data['middle_name_ar'] = filtered_names[1]+' '+filtered_names[2]
            # except Exception as e:
            #     front_data['first_name_ar'] = ''
            #     front_data['last_name_ar'] = ''
            #     front_data['middle_name_ar'] = ''

            try:
                list_1 = front_data['name_ar'].split(" ")
                filtered_names = [name for name in list_1 if len(name) > 1]
                if len(filtered_names) == 4:
                    front_data['first_name_ar'] = filtered_names[0]
                    front_data['last_name_ar'] = filtered_names[3]
                    front_data['middle_name_ar'] = filtered_names[1] + ' ' + filtered_names[2]
                elif len(filtered_names) == 5:
                    front_data['first_name_ar'] = filtered_names[0]
                    front_data['last_name_ar'] = filtered_names[4]
                    front_data['middle_name_ar'] = filtered_names[1] + ' ' + filtered_names[2] + ' ' + filtered_names[3]
                elif len(filtered_names) == 6:
                    front_data['first_name_ar'] = filtered_names[0]
                    front_data['last_name_ar'] = filtered_names[5]
                    front_data['middle_name_ar'] = filtered_names[1] + ' ' + filtered_names[2] + ' ' + filtered_names[
                        3] + ' ' + filtered_names[4]
                elif len(filtered_names) == 7:
                    front_data['first_name_ar'] = filtered_names[0]
                    front_data['last_name_ar'] = filtered_names[6]
                    front_data['middle_name_ar'] = filtered_names[1] + ' ' + filtered_names[2] + ' ' + filtered_names[
                        3] + ' ' + filtered_names[4] + ' ' + filtered_names[5]
                else:
                    front_data['first_name_ar'] = ''
                    front_data['last_name_ar'] = ''
                    front_data['middle_name_ar'] = ''

            except Exception as e:
                front_data['first_name_ar'] = ''
                front_data['last_name_ar'] = ''
                front_data['middle_name_ar'] = ''

            return front_data
                
    def extract_back_id_info(self, back_id, country):
        if country == 'UAE':
                from idvpackage.ocr_utils import  detect_photo_on_screen, detect_screenshot, document_on_printed_paper
                from idvpackage.common import remove_special_characters1, remove_special_characters2, func_id_number, convert_date_format, detect_id_card_uae, convert_gender, count_digits_after_pattern, remove_special_characters_mrz2, validate_string
                from idvpackage.ocr_utils import is_valid_and_not_expired
                import pytesseract
                import sys
                back_data = {
                    'error': '',
                    'doc_type': 'national_identity_card'
                }

                try:
                    processed_back_id = self.image_conversion(back_id)
                    id_infos= self.get_ocr_results(processed_back_id)
                    text = id_infos[0].description
                    pattern4 = r'(Card Number|<<|ILARE|IDARE|(?=.*\bOccupation\b).*|(?=.*\bEmployer\b).*|(?=.*\bIssuing Place\b).*)'
                    k= re.search(pattern4, text.replace(" ",""), re.IGNORECASE)

                    if not k:
                        back_data["error"] = "not_back_id"
                        return back_data

                    original_text = text

                    # print('this is original text:',original_text)
                    
                  
                    patterns = {
                        'id_number': (r'(?:ILARE|IDARE)\s*([\d\s]+)', lambda match: match.group(0).replace(" ", "")[15:30] if match else ''),
                        'card_number': (r'(?:ILARE|IDARE)(\d{1,9})', lambda match: match.group(1) if match else ''),
                        'nationality': (r'([A-Z]+)<<', lambda match: match.group(1) if match else ''),
                        'gender': (r'(?<=\d)[A-Z](?=\d)', lambda match: match.group(0) if match else ''),
                        'dob': (r'(\d+)[MF]', lambda match: self.convert_dob(match.group(1)) if match else ''),
                        'expiry_date': (r'[MF](\d+)', lambda match: self.convert_expiry_date(match.group(1)) if match else ''),
                        'name': (r'(.*[A-Za-z]+<[<]+[A-Za-z].*)', lambda match: match.group(0).replace('<', ' ').strip() if match else ''),
                        #'first_name': (r'<<([^<]+)', lambda match: match.group(0).replace("<", "") if match else ''),
                        #'last_name': (r'([^<]+)(?=<<)', lambda match: match.group(0).replace("<", "") if match else ''),
                        # 'occupation': (r'Occupation:\s*([-\w\s.]+)', lambda match: match.group(1).strip().split('\n', 1)[0] if match else '', re.IGNORECASE),
                        # 'employer': (r'Employer:\s*([\w\s.]+)', lambda match: match.group(1).strip().split('\n', 1)[0] if match else '', re.IGNORECASE),
                        'issuing_place': (r'Issuing Place:\s*([\w\s.]+)', lambda match: match.group(1).strip().split('\n', 1)[0] if match else '', re.IGNORECASE)
                    }

                    mrz_pattern = r'(ILAR.*\n*.*\n*.*\n*.*|IDAR.*\n*.*\n*.*\n*.*)'

                    try: 
                        mrz = re.findall(mrz_pattern, original_text.replace(" ","").strip(), re.MULTILINE)
                        mrz_list=mrz[0].replace(" ", "").split("\n", 3)
                        mrz1=mrz_list[0]

                    except:
                        mrz1=''

                    #### EXTRACT mrz2

                    # try:
                    #     mrz2=mrz_list[1]
                    # except:
                    #     mrz2=''
                    try:
       
                         mrz2=[s for s in [remove_special_characters1(ele).replace(' ','') for ele in original_text.split('\n')] if len(re.findall(r'<', s)) >= 2 and not (re.fullmatch(r'[A-Za-z<]+', s))][0]

                    except:
               
                         mrz2=''
                    ### Extract mrz3
                    try:
                        mrz3=[s for s in [remove_special_characters1(ele).replace(' ','') for ele in original_text.split('\n')] if len(re.findall(r'<', s)) >= 2 and re.fullmatch(r'[A-Za-z<]+', s)][0]
                        back_data['name']=remove_special_characters2(mrz3[0]).strip()
                        back_data['last_name']=remove_special_characters2(re.search(r'([^<]+)(?=<<)', mrz3).group(0)).strip() if re.search(r'([^<]+)(?=<<)', mrz3) else ''
                        back_data['first_name']=remove_special_characters2(re.search(r'<<([^<]+)', mrz3).group(0)).strip() if re.search(r'<<([^<]+)', mrz3) else ''

                    except:
                        mrz3,back_data['name'],back_data['last_name'],back_data['first_name']='','','',''

                    pattern = r'ARE\d{25}'

                    extracted_data_tesseract = ''
      

                    if not re.search(pattern,original_text.replace(' ','')):

                        img=self.image_conversion(back_id)
                        # Decode the base64 string
                        image_data = base64.b64decode(back_id)
                        # Convert to an image
                        with Image.open(io.BytesIO(image_data)) as image:
                            # Use PyTesseract to do OCR on the image
                            try:
                                extracted_data_tesseract = pytesseract.image_to_string(image)
                                match = re.search(pattern,extracted_data_tesseract.replace(' ',''))
                                mrz1=(mrz1[:2]+match[0]).strip()
                            except: 
                                pass
                    
                    mrz1_keys = ['id_number', 'card_number']
                    mrz2_keys = ['nationality', 'gender', 'dob', 'expiry_date']
                    #mrz3_keys = [ 'first_name', 'last_name']
                    
                    for key, value in patterns.items():
                        pattern = value[0]
                        transform_func = value[1]
                        flags = value[2] if len(value) > 2 else 0

                        text = original_text
                        if key in mrz1_keys:
                            text = mrz1
                        if key in mrz2_keys:
                            text = mrz2
                        # if key in mrz3_keys:
                        #     text = mrz3

                        match = re.search(pattern, text, flags)
                        back_data[key] = transform_func(match) if match else ''
                    
                    back_data.update({
                        'mrz1':mrz1,
                        'mrz2':mrz2,
                        'mrz3':mrz3
                    })

                    # print("ths is gender :",back_data['gender'])

                    ## extracting occupation and employer
                    occ_word='Occupation'
                    occ=''
                    emp_word='Employer'
                    emp=''
                    try:
                        lines=original_text.split('\n')  
                        for line in lines:
                            if occ_word in line:
                                start_index = line.find(occ_word)
                                end_index = start_index + len(occ_word) 
                                occ = line[end_index:]
                                occ = self.clean_string(occ)

                            if emp_word in line:
                                start_index1 = line.find(emp_word)
                                end_index1 = start_index1 + len(emp_word) 
                                emp = line[end_index1:]
                                emp = self.clean_string(emp)
                    except:
                        occ = ''
                        emp = ''

                    family_sponsor_word = 'Family Sponsor'
                    family_sponsor=''
                    try:
                        lines=original_text.split('\n')  
                        for line in lines:
                            if family_sponsor_word in line:
                                start_index = line.find(family_sponsor_word)
                                end_index = start_index + len(family_sponsor_word) 
                                family_sponsor = line[end_index:]
                                family_sponsor = self.clean_string(family_sponsor)
                    except:
                        family_sponsor = ''

                    ### new rule
                    if len(str(back_data['id_number']))!=15:
                        back_data['id_number']=''
                    
                    ### new rule
                    if len(str(back_data['card_number']))!=9:
                        back_data['card_number']=''
                    

                    current_module = sys.modules[__name__] 

                    for key in ['dob','expiry_date','card_number','name','nationality']:
                        #if not back_data[key] and key not in ['occupation', 'employer', 'first_name', 'last_name', 'issuing_place', 'error']:
                        
                        if not back_data[key]:
                                transform_func_new = getattr(current_module,f'func_{key}')
                                back_data[key] = transform_func_new(original_text)

                    for key in ['dob','expiry_date']:
                         if not back_data[key]:
                             transform_func_new = getattr(current_module,f'find_{key}')
                             back_data[key] = transform_func_new(original_text,back_data['mrz2'])
                    
                    if not back_data['id_number']:
                        back_data['id_number']=func_id_number(original_text,back_data['dob'])

                    if is_valid_and_not_expired(back_data.get('expiry_date'), country) == 'consider':
                        back_data['error'] = 'expired_document'
                    
                    ### convert the date format
                    if back_data['dob']:
                        try:
                            back_data['dob']=convert_date_format(back_data['dob'])
                        except:
                            back_data['dob']=''

                    if back_data['expiry_date']:
                        try:
                            back_data['expiry_date']=convert_date_format(back_data['expiry_date'])
                        except:
                            back_data['expiry_date']=''

                    img = self.image_conversion(back_id)
                    if hasattr(img, '_getexif'):
                        orientation = 0x0112
                        exif = img._getexif()
                        if exif is not None and orientation in exif:
                            orientation = exif[orientation]
                            rotations = {
                                3: Image.ROTATE_180,
                                6: Image.ROTATE_270,
                                8: Image.ROTATE_90
                            }
                            if orientation in rotations:
                                img = img.transpose(rotations[orientation])

                    image = np.array(img)
                    pil_image = Image.fromarray(image)

                    with io.BytesIO() as output:
                        pil_image.save(output, format="PNG")
                        image_data = output.getvalue()

                    tampered_result, third_part_text = detect_id_card_uae(self.client, image_data, id_infos, part='third')
                    back_data['back_tampered_result'] = tampered_result

                    ### layer of gender extraction
                    if not back_data['gender']:
                        # print(f"TEXT: {third_part_text}")
                        mrz2 = re.search(r'\b\d{7}.*?(?:<<\d|<<\n)', third_part_text)
                        mrz2 = mrz2.group(0) if mrz2 else None

                        gender_ptrn = r'\d{7}([A-Z])\d{4,}'
                        if mrz2:
                            gender_match = re.search(gender_ptrn, mrz2)
                            gender = gender_match.group(1)
                            back_data['gender'] = gender
                        else:
                            gender_match = re.search(gender_ptrn, third_part_text)
                            gender = gender_match.group(0)
                            back_data['gender'] = gender

                ### another layer of gender extraction + formatting
                    if not back_data['gender']:
                        extract_no_space = original_text.replace(' ','')
                        try:
                            pattern = r'\sM|F'
                            m = re.search(pattern, original_text)
                            back_data['gender'] = m.group(0)[-1]
                        except:
                            pattern = r'\d{3}(?:M|F)\d'
                            m = re.findall(pattern, extract_no_space)
                            if len(m) != 0:
                                back_data['gender'] = m[0][3:4]
                            else:
                                back_data['gender'] = ''

                ### if still no gender then one more layer of gender extraction + formatting
                    if not back_data['gender']:
                       
                        if not extracted_data_tesseract:
                                # Decode the base64 string
                                image_data = base64.b64decode(back_id)
                                # Convert to an image
                                with Image.open(io.BytesIO(image_data)) as image:
                                    # Use PyTesseract to do OCR on the image
                                    extracted_data_tesseract = pytesseract.image_to_string(image)

                        mrzs_tesseract = [s for s in [ele.replace(' ','') for ele in extracted_data_tesseract.split('\n')] if re.search(r'<<{2,}', s)]
                        mrz3_tesseract=[s for s in mrzs_tesseract if re.fullmatch(r'[A-Za-z<]+', s)]
                        
                        if mrzs_tesseract and mrz3_tesseract:
                            mrz2_tesseract=list(set(mrzs_tesseract)-set(mrz3_tesseract))[0]
                            gender=mrz2_tesseract[7].lower()
                            if gender in ['f','m']:
                                back_data['gender']=convert_gender(gender)
                    else:
                        back_data['gender']=convert_gender(back_data['gender'])

                    if back_data['name']:
                         back_data['name'] =re.sub('[^a-zA-Z]', ' ', back_data['name']).strip()

                     ### new rule
                    if len(str(back_data['id_number']))!=15:
                            back_data['id_number']=''
                    
                    ### new rule
                    if len(str(back_data['card_number']))!=9:
                        back_data['card_number']=''

                    count = count_digits_after_pattern(mrz2)

                    if count>1:
                        mrz2=mrz2[:-int(count-1)]

                    ## fix a special case where O comes as zero

                    if re.sub(r'O([A-Z]{3})', r'0\1', mrz2):
                        mrz2=re.sub(r'O([A-Z]{3})', r'0\1', mrz2)

                    if not validate_string(remove_special_characters_mrz2(mrz2)):
                        if (back_data['mrz2']) and (back_data['gender']) and (back_data['mrz2'][-1].isdigit()):
                            try:
                                # Regular expression to extract two sequences of 7 digits
                                matches = re.findall(r'\d{7}', back_data['mrz2'])

                                # Check if we found two sequences
                                extracted_digits = matches[:2] if len(matches) >= 2 else None

                                if extracted_digits:
                             
                                   mrz2=extracted_digits[0]+back_data['gender'][:1]+extracted_digits[-1]+'<<<<<<<<<<<'+back_data['mrz2'][-1]
                            except:
                                mrz2=''                           
                        else:
                            mrz2=''

                    if len(back_data['nationality'])>3:
                            back_data['nationality']=back_data['nationality'][-3:]

                    ### check if teh extracted nationality is valid
                    valid_nationality_result = self.check_nationality_in_iso_list(back_data.get('nationality'))

                    img = self.image_conversion(back_id)
                    image = np.array(img)
                    # pil_image = Image.fromarray(image)
                    
                    doc_on_pp_result = document_on_printed_paper(image)
                    screenshot_result = detect_screenshot(self.client, back_id)
                    photo_on_screen_result = detect_photo_on_screen(self.client, back_id)
                    back_blurred, back_glare = self.get_blurred_and_glared_for_doc(image)
                    # print(f"blurred, glare: {back_blurred, back_glare}")

                    back_data_update = {
                        'valid_nationality': valid_nationality_result,
                        'back_extracted_data': original_text,
                        'back_coloured': True,
                        'mrz': mrz,
                        'mrz1': mrz1,
                        'mrz2': mrz2,
                        'mrz3': mrz3,
                        'occupation': occ,
                        'employer': emp,
                        'family_sponsor': family_sponsor,
                        'doc_on_pp': doc_on_pp_result,
                        'screenshot_result': screenshot_result,
                        'photo_on_screen_result': photo_on_screen_result,
                        'back_blurred': back_blurred, 
                        'back_glare': back_glare
                    }
                    
                   
                    back_data.update(back_data_update)

                    non_optional_keys = ["id_number", "card_number", "name", "dob", "expiry_date", "gender", "nationality", "mrz", "mrz1", "mrz2", "mrz3"]
                    empty_string_keys = [key for key, value in back_data.items() if key in non_optional_keys and value == '']

            
                    if empty_string_keys:
                        back_data['error'] = 'covered_photo'

                  
                except Exception as e:
                    back_data['error'] = 'bad_image'
                    back_data['error_details'] = e


                return back_data

        if country == 'IRQ':
                back_data = {
                    'error': '',
                    'doc_type': 'national_identity_card'
                }
                from idvpackage.ocr_utils import  detect_photo_on_screen, detect_screenshot, document_on_printed_paper
                from idvpackage.iraq_id_extraction import iraq_back_id_extraction

                try:
                    st = time.time()
                    processed_back_id = self.image_conversion(back_id)
                    logging.info(f'----------------Time taken for image conversion back: {time.time() - st} seconds\n')

                    st = time.time()
                    compressed_image = BytesIO()
                    processed_back_id.save(compressed_image, format="JPEG", quality=60, optimize=True)
                    compressed_image_data = compressed_image.getvalue()
                    
                    id_infos = self.get_ocr_results(compressed_image_data, country = 'IRQ', side = 'front')
                    text = id_infos[0].description
                    logging.info(f'----------------Time taken for vision back: {time.time() - st} seconds\n')

                    # print(f"\nORIGINAL TEXT: {text}\n")
                    # translated_id_text = self.translator.translate(text, src='ar', dest='en').text
                    try:
                        translated_id_text = self.translator.translate(text, src='ar', dest='en').text
                    except Exception as e:
                        logging.info(f'--------------Fallback for translation keyword matching\n')
                        from deep_translator import GoogleTranslator
                        translated_id_text = GoogleTranslator('ar', 'en').translate(text)
            
                    # print(f"\nTRANS: {translated_id_text}\n")

                    pattern4 = r'(Register|Signature|IDIRQ|Family number|The Directorate of Nationality)'
                    k= re.search(pattern4, translated_id_text, re.IGNORECASE)
                    
                    if not k:
                        k1 = re.search(pattern4, text, re.IGNORECASE)
                        if not k1:
                            back_data["error"] = "not_back_id"
                            return back_data

                    original_text = text

                    # print('this is original text:',original_text)
                    image = np.array(processed_back_id)
                    # pil_image = Image.fromarray(image)

                    # with io.BytesIO() as output:
                    #     pil_image.save(output, format="PNG")
                    #     image_data = output.getvalue()

                    # template_result='clear'
                    # tampered_result_back = calculate_error_difference(np.array(Image.open(io.BytesIO(base64.decodebytes(bytes(back_id, "utf-8"))))))
                    
                    # image_format = 'jpg'
                    image_format = self.detect_image_format(back_id)

                    st = time.time()
                    back_extraction_result = iraq_back_id_extraction(self.client, compressed_image_data, id_infos, original_text, image_format)
                    logging.info(f'--------------Time taken for back id data formatting final extraction: {time.time() - st}\n')

                    error = ""
                    expiry_date = back_extraction_result.get('expiry_date', '')

                    if expiry_date:
                        try:
                            dt_obj = datetime.strptime(expiry_date, '%d/%m/%Y')
                            tomorrow = datetime.today() + timedelta(days=1)
                            if dt_obj < tomorrow:
                                back_data['error'] = 'expired_id'
                        except:
                            pass
                        
                    back_data.update(back_extraction_result)

                    st = time.time()
                    valid_nationality_result = self.check_nationality_in_iso_list(back_data.get('nationality'))
                    doc_on_pp_result = document_on_printed_paper(image)
                    screenshot_result = detect_screenshot(self.client, back_id)
                    photo_on_screen_result = detect_photo_on_screen(self.client, back_id)
                    back_blurred, back_glare = self.get_blurred_and_glared_for_doc(image)
                    logging.info(f'----------------Time taken for fraud detection attributes back: {time.time() - st} seconds\n')

                    back_data_update = {
                            # 'back_tampered_result': tampered_result_back,
                            'valid_nationality': valid_nationality_result,
                            'back_extracted_data': original_text,
                            'translated_back_id_text': translated_id_text,
                            'back_coloured': True,
                            'occupation': '',
                            'employer': '',
                            'doc_on_pp': doc_on_pp_result,
                            'screenshot_result': screenshot_result,
                            'photo_on_screen_result': photo_on_screen_result,
                            'back_blurred': back_blurred, 
                            'back_glare': back_glare
                        }
                    
                    back_data.update(back_data_update)
                                    
                    non_optional_keys = ["id_number", "card_number", "dob", "expiry_date", "nationality"]
                    empty_string_keys = [key for key, value in back_data.items() if key in non_optional_keys and value == '']

                    if empty_string_keys:
                        back_data['error'] = 'covered_photo'

                except Exception as e:
                    back_data['error'] = 'bad_image'
                    back_data['error_details'] = e



                return back_data

        if country == 'QAT':
                from idvpackage.qatar_id_extraction import qatar_back_id_extraction

                back_data = {
                    'error': '',
                    'doc_type': 'national_identity_card'
                }
                #is_colored2 = self.is_colored(back_id)
                #if is_colored2:
                try:
                    processed_back_id = self.image_conversion(back_id)
                    
                    compressed_image = BytesIO()
                    processed_back_id.save(compressed_image, format="JPEG", quality=80, optimize=True)
                    compressed_image_data = compressed_image.getvalue()
                    
                    id_infos = self.get_ocr_results(compressed_image_data, country='QAT')

                    text = id_infos[0].description
                    # print(f'Original text: {text}')

                    #translated_id_text = self.translator.translate(text, src='ar', dest='en').text
                    pattern4 = r'(Director General of the General Department|Directorate of Passports|Passport number|Serial)'
                    k= re.search(pattern4, text, re.IGNORECASE)
                    # print('this is translated_id_text',translated_id_text)
                    if not k:
                        back_data["error"] = "not_back_id"
                        
                        return back_data

                    original_text = text

                    # print('this is original text:',original_text)

                    ## TODO: template matching for Qatar ID's
                    image_data = base64.b64decode(back_id)
                    
                    back_extraction_result = qatar_back_id_extraction(original_text)
                    back_data.update(back_extraction_result)

                    # image = np.array(processed_back_id)

                    back_data_update = {
                            # 'back_tampered_result': tampered_result_back,
                            'back_extracted_data': original_text,
                            'back_coloured': True,
                            'doc_on_pp': 'clear',
                            'screenshot_result': 'clear',
                            'photo_on_screen_result': 'clear',
                            'back_blurred': 'clear', 
                            'back_glare': 'clear'
                        }
                    
                    back_data.update(back_data_update)

                # non_optional_keys = ["card_number"]
                # empty_string_keys = [key for key, value in back_data.items() if key in non_optional_keys and value == '']

                # if empty_string_keys:
                #     back_data['error'] = 'covered_photo'

                except Exception as e:
                    back_data['error'] = 'bad_image'
                    back_data['error_details'] = e

                    #back_data['error_details'] = e
            # else:
            #     back_data['error'] = 'bad_image'

                return back_data
        
        if country == 'LBN':
                from idvpackage.ocr_utils import detect_logo, detect_photo_on_screen, detect_screenshot, document_on_printed_paper
                from idvpackage.lebanon_id_extraction import lebanon_back_id_extraction
                from idvpackage.blur_detection import is_image_blur

                back_data = {
                    'error': '',
                    'doc_type': 'national_identity_card'
                }


                try:
                    st = time.time()
                    processed_back_id = self.image_conversion(back_id)
                    logging.info(f'----------------Time taken for image conversion back: {time.time() - st} seconds\n')
                    
                    st = time.time()
                    compressed_image = BytesIO()
                    processed_back_id.save(compressed_image, format="JPEG", quality=100, optimize=True)
                    compressed_image_data = compressed_image.getvalue()
                    id_infos = self.get_ocr_results(compressed_image_data, country='LBN')
                    text = id_infos[0].description
                    logging.info(f'----------------Time taken for vision back: {time.time() - st} seconds\n')

                    try:
                        translated_id_text = self.translator.translate(text, src='ar', dest='en').text
                    except:
                        logging.info(f'\n--------------Fallback for translation keyword matching')
                        from deep_translator import GoogleTranslator
                        translated_id_text = GoogleTranslator('ar', 'en').translate(text)
                    logging.info(f'----------------Time taken for ar-en translation back: {time.time() - st} seconds\n')

                    pattern4 = r'(Marital status|Family|Release|Sex|Register number)'
                    k= re.search(pattern4, translated_id_text, re.IGNORECASE)
                    
                    if not k:
                        back_data["error"] = "not_back_id"
                        return back_data

                    # Check for Civil status document keywords in translated text
                    civil_status_keywords = r'(General Directorate|Convergence|Municipalities|Personal Status|statement of individuals)'
                    civil_status_match = re.search(civil_status_keywords, translated_id_text, re.IGNORECASE)
                    
                    if civil_status_match:
                        back_data["error"] = "not_back_id"
                        return back_data
                    
                    # Check for blur using the new comprehensive method
                    image = np.array(processed_back_id)
                    if is_image_blur(image, laplace_threshold=30, canny_threshold=1500, checks_threshold=120, min_area_percentage=1.0):
                        print(f"Blur/Brightness issue detected in front image, marking as covered photo")
                        back_data['error'] = 'blur_photo'
                        return back_data

                    ## TODO: template matching for Lebanon ID's
                    # image_data = base64.b64decode(back_id)
                    # template_result = detect_logo(self.client, image_data, country, compare_type='template', side='back')
                    # if template_result == 'consider':
                    #     back_data["error"] = "not_back_id"
                    #     return back_data
                    
                    ## TODO: tampering result for Lebanon ID's
                    # tampered_result_back = calculate_error_difference(np.array(Image.open(io.BytesIO(base64.decodebytes(bytes(back_id, "utf-8"))))))
                    
                    st = time.time()
                    back_extraction_result = lebanon_back_id_extraction(text)
                    logging.info(f'----------------Time taken for data formatting and final extraction back: {time.time() - st} seconds\n')

                    back_data.update(back_extraction_result)
                    image = np.array(processed_back_id)
                    
                    st = time.time()
                    ## TODO: doc_on_pp and detect_photo_on_screen for LBN
                    # doc_on_pp_result = document_on_printed_paper(image)
                    doc_on_pp_result = 'clear'
                    screenshot_result = detect_screenshot(self.client, back_id)
                    # photo_on_screen_result = detect_photo_on_screen(self.client, image)
                    photo_on_screen_result = 'clear'
                    back_blurred, back_glare = self.get_blurred_and_glared_for_doc(image)
                    logging.info(f'----------------Time taken for fraud detection attributes back: {time.time() - st} seconds\n')

                    back_data_update = {
                            # 'back_tampered_result': tampered_result_back,
                            'back_extracted_data': text,
                            'translated_back_id_text': translated_id_text,
                            'back_coloured': True,
                            'doc_on_pp': doc_on_pp_result,
                            'screenshot_result': screenshot_result,
                            'photo_on_screen_result': photo_on_screen_result,
                            'back_blurred': back_blurred,
                            'back_glare': back_glare
                        }
                    
                    back_data.update(back_data_update)
                    
                    if not back_data.get('gender') or not back_data.get('issue_date') or not back_data.get('card_number'):
                        back_data['error'] = 'covered_photo'

                    non_optional_keys = ["gender", "issue_date"]
                    empty_string_keys = [key for key, value in back_data.items() if key in non_optional_keys and value == '']

                    if empty_string_keys:
                        back_data['error'] = 'covered_photo'

                except Exception as e:
                    back_data['error'] = 'bad_image'
                    back_data['error_details'] = e
            


                return back_data

        if country == 'SDN':
            from idvpackage.ocr_utils import detect_logo, detect_photo_on_screen, detect_screenshot, \
                document_on_printed_paper
            from idvpackage.sudan_id_extraction import sdn_back_id_extraction
            from idvpackage.blur_detection import is_image_blur

            back_data = {
                'error': '',
                'doc_type': 'national_identity_card'
            }

            try:
                st = time.time()
                processed_back_id = self.image_conversion(back_id)
                logging.info(f'----------------Time taken for image conversion back: {time.time() - st} seconds\n')

                st = time.time()
                compressed_image = BytesIO()
                processed_back_id.save(compressed_image, format="JPEG", quality=90, optimize=True)
                compressed_image_data = compressed_image.getvalue()
                id_infos = self.get_ocr_results(compressed_image_data, country='SDN')
                text = id_infos[0].description
                logging.info(f'----------------Time taken for vision back: {time.time() - st} seconds\n')

                try:
                    translated_id_text = self.translator.translate(text, src='ar', dest='en').text
                except:
                    logging.info(f'\n--------------Fallback for translation keyword matching')
                    from deep_translator import GoogleTranslator
                    translated_id_text = GoogleTranslator('ar', 'en').translate(text)
                logging.info(f'----------------Time taken for ar-en translation back: {time.time() - st} seconds\n')

                pattern4 = r'(IDSDN|Name)'
                k = re.search(pattern4, translated_id_text, re.IGNORECASE)

                if not k:
                    back_data["error"] = "not_back_id"
                    return back_data

                image = np.array(processed_back_id)
                # blur_test = is_image_blur(image)
                # if blur_test == True:
                #     print(f"Back ID Document is blurry, marking as covered photo")
                #     back_data['error'] = 'covered_photo'
                #     return back_data

                ## TODO: template matching for Lebanon ID's
                # image_data = base64.b64decode(back_id)
                # template_result = detect_logo(self.client, image_data, country, compare_type='template', side='back')
                # if template_result == 'consider':
                #     back_data["error"] = "not_back_id"
                #     return back_data

                ## TODO: tampering result for Lebanon ID's
                # tampered_result_back = calculate_error_difference(np.array(Image.open(io.BytesIO(base64.decodebytes(bytes(back_id, "utf-8"))))))

                st = time.time()
                back_extraction_result = sdn_back_id_extraction(text)
                logging.info(
                    f'----------------Time taken for data formatting and final extraction back: {time.time() - st} seconds\n')

                if back_extraction_result.get('expiry_date'):
                    try:
                        expiry_date = back_extraction_result['expiry_date']
                        expiry_date = datetime.strptime(expiry_date, "%d/%m/%Y")
                        current_date = datetime.now()

                        if expiry_date < current_date:
                            back_data['error'] = 'expired_id'
                            return back_data
                    except:
                        pass

                if back_extraction_result.get('dob_back'):
                    try:
                        dob_date = back_extraction_result['dob_back']
                        dob_date = datetime.strptime(dob_date, "%d/%m/%Y")
                        current_date = datetime.now()
                        if dob_date > current_date:
                            back_data['error'] = 'expired_id'
                            return back_data
                    except:
                        pass

                back_data.update(back_extraction_result)

                if back_data.get('issue_date'):
                    back_data['issuance_date'] = back_data['issue_date']

                image = np.array(processed_back_id)

                st = time.time()
                ## TODO: doc_on_pp and detect_photo_on_screen for LBN
                # doc_on_pp_result = document_on_printed_paper(image)
                doc_on_pp_result = 'clear'
                screenshot_result = detect_screenshot(self.client, back_id)
                # photo_on_screen_result = detect_photo_on_screen(self.client, image)
                photo_on_screen_result = 'clear'
                back_blurred, back_glare = self.get_blurred_and_glared_for_doc(image)
                logging.info(
                    f'----------------Time taken for fraud detection attributes back: {time.time() - st} seconds\n')

                back_data_update = {
                    # 'back_tampered_result': tampered_result_back,
                    'back_extracted_data': text,
                    'translated_back_id_text': translated_id_text,
                    'back_coloured': True,
                    'doc_on_pp': doc_on_pp_result,
                    'screenshot_result': screenshot_result,
                    'photo_on_screen_result': photo_on_screen_result,
                    'back_blurred': back_blurred,
                    'back_glare': back_glare
                }

                back_data.update(back_data_update)

                non_optional_keys = ["gender", "expiry_date"]
                empty_string_keys = [key for key, value in back_data.items() if
                                     key in non_optional_keys and value == '']

                if empty_string_keys:
                    back_data['error'] = 'missing_key_fields'

            except Exception as e:
                back_data['error'] = 'Something went wrong'
                print(f"-------------->> Something went wrong error trace:: {e}")
                back_data['error_details'] = e

            return back_data
        
    def exract_passport_info(self, passport, country):
        if country.upper() == 'RUS':
            processed_passport = self.image_conversion(passport)
            passport_text = self.get_ocr_results(processed_passport)
            passport_text = passport_text[0].description

            passport_details = {}

            patterns = {
                'passport_given_name': (r'Имя Given names\n(.*?)/', lambda match: self.translator.translate(match.group(1), src='ru', dest='en').text if match else ''),
                'passport_surname': (r'RUS(.*?)<<(.*?)<.*', lambda match: match.group(1) if match else ''),
                'passport_number': (r"(\d{7})", lambda match: match.group(1) if match else ''),
                'passport_date_of_birth': (r'(\d+)[MF]', lambda match: self.convert_dob(match.group(1)) if match else ''),
                'passport_date_of_expiry': (r'[MF](\d+)', lambda match: self.convert_expiry_date(match.group(1)) if match else ''),
                'passport_gender': (r'(\d)([A-Za-z])(\d)', lambda match: match.group(2) if match else '')
            }

            mrz1_pattern = r'([A-Z<]+)<<([A-Z<]+)<<([\dA-Z<]+)'
            mrz2_pattern = r'(\d{10}[A-Z]{3}\d{7}[\dA-Z<]+)'

            mrz1_matches = re.findall(mrz1_pattern, passport_text)
            mrz2_matches = re.findall(mrz2_pattern, passport_text)

            if mrz1_matches:
                mrz1 = ' '.join(mrz1_matches[0])
            else:
                mrz1 = ''

            if mrz2_matches:
                mrz2 = mrz2_matches[0]
            else:
                mrz2 = ''

            mrz1_keys = ['passport_surname']
            mrz2_keys = ['passport_date_of_birth', 'passport_date_of_expiry', 'passport_gender']

            for key, value in patterns.items():
                pattern = value[0]
                transform_func = value[1]

                text = passport_text
                if key in mrz1_keys:
                    text = mrz1
                if key in mrz2_keys:
                    text = mrz2

                match = re.search(pattern, text)
                passport_details[key] = transform_func(match) if match else ''

            passport_details['doc_type'] = 'passport'

            return passport_details
        
        if country.upper() == 'IRQ':
                from idvpackage.ocr_utils import detect_photo_on_screen, detect_screenshot, document_on_printed_paper
                from idvpackage.iraq_passport_extraction import iraq_passport_extraction
                from idvpackage.common import load_and_process_image_deepface

                passport_data = {
                    'error': '',
                    'doc_type': 'passport'
                }
            

                try:
                    processed_passport= self.image_conversion(passport)
                    id_infos = self.get_ocr_results(processed_passport)
                    passport_text = id_infos[0].description
                    pattern4 = r'(Republic of Iraq|Iraq|Passport)'
                    k= re.search(pattern4, passport_text, re.IGNORECASE)

                    if not k:
                        passport_data["error"] = "not_passport"
                        return passport_data

                    original_text = passport_text

                    passport_details = iraq_passport_extraction(passport_text)
                    
                    passport_details['id_number'] = passport_details.get('passport_number', passport_details.get('passport_number_mrz'))
                    if passport_details.get('passport_number_mrz') and passport_details['id_number']!=passport_details['passport_number_mrz']:
                        passport_details['id_number'] = passport_details['passport_number_mrz']

                    if not passport_details.get('passport_date_of_birth_generic') and passport_details.get('dob_mrz'):
                        passport_details['dob'] = passport_details['dob_mrz']
                    else:
                        passport_details['dob'] = passport_details['passport_date_of_birth_generic']

                    if (not passport_details.get('full_name') and passport_details.get('full_name_generic')) or (len(passport_details.get('full_name')) < len(passport_details.get('full_name_generic'))):
                        if passport_details['full_name_generic'].startswith('IRQ'):
                            passport_details['full_name'] = passport_details['full_name_generic'][3:].strip()
                        else:
                            passport_details['full_name'] = passport_details['full_name_generic']
                    
                    # if len(passport_details.get('full_name')) < len(passport_details.get('full_name_generic')):
                    #     passport_details['full_name'] = passport_details['full_name_generic']

                    if not passport_details.get('last_name') and passport_details.get('surname_generic'):
                        if passport_details['surname_generic'].startswith('IRQ'):
                            passport_details['last_name'] = passport_details['surname_generic'][3:].strip()
                        else:
                            passport_details['last_name'] = passport_details['surname_generic']

                    if not passport_details.get('passport_date_of_expiry_generic') and passport_details.get('expiry_date_mrz'):
                        passport_details['expiry_date'] = passport_details['expiry_date_mrz']
                    else:
                        passport_details['expiry_date'] = passport_details['passport_date_of_expiry_generic']
                    
                    keys_to_delete = ['expiry_date_mrz', 'passport_date_of_expiry_generic', 'dob_mrz', 'passport_date_of_birth_generic', 
                                    'passport_number', 'passport_number_mrz', 'full_name_generic', 'surname_generic']
                    
                    for key in keys_to_delete:
                        del passport_details[key]

                    passport_data.update(passport_details)

                    image = np.array(processed_passport)
                    doc_on_pp_result = document_on_printed_paper(image)
                    screenshot_result = detect_screenshot(self.client, passport)
                    photo_on_screen_result = detect_photo_on_screen(self.client, passport)
                    blurred, glare = self.get_blurred_and_glared_for_doc(image)
                    valid_nationality_result = self.check_nationality_in_iso_list(passport_details.get('nationality'))

                    front_face_locations, front_face_encodings = load_and_process_image_deepface(passport)

                    front_face_locations_str = json.dumps([tuple(face_loc) for face_loc in front_face_locations])
                    front_face_encodings_str = json.dumps([face_enc.tolist() for face_enc in front_face_encodings])

                    passport_data_update = {
                            # 'back_tampered_result': tampered_result_back,
                            'passport_data': original_text,
                            'front_coloured': True,
                            'back_coloured': True,
                            'front_logo_result': 'clear',
                            'front_doc_on_pp': doc_on_pp_result,
                            'front_screenshot_result': screenshot_result,
                            'front_photo_on_screen_result': photo_on_screen_result,
                            'doc_on_pp': doc_on_pp_result,
                            'screenshot_result': screenshot_result,
                            'photo_on_screen_result': photo_on_screen_result,
                            'front_blurred': blurred, 
                            'front_glare': glare,
                            'back_blurred': blurred, 
                            'back_glare': glare,
                            'front_face_locations': front_face_locations_str,
                            'front_face_encodings': front_face_encodings_str,
                            'valid_nationality': valid_nationality_result,
                            'issuing_country': 'IRQ'
                        }
                    
                    passport_data.update(passport_data_update)
                                    
                    non_optional_keys = ["gender", "passport_date_of_birth", "id_number", "passport_date_of_expiry"]
                    empty_string_keys = [key for key, value in passport_data.items() if key in non_optional_keys and value == '']

                    if empty_string_keys:
                        passport_data['error'] = 'covered_photo'

                except Exception as e:
                    passport_data['error'] = 'bad_image'
                    passport_data['error_details'] = e


                return passport_data

        if country.upper() == 'LBN':
                logging.info("-------------Working on LBN Passport \n")
                from idvpackage.ocr_utils import  detect_photo_on_screen, detect_screenshot, document_on_printed_paper
                from idvpackage.lebanon_passport_extraction import lebanon_passport_extraction
                from idvpackage.common import load_and_process_image_deepface
                from idvpackage.blur_detection import is_image_blur

                passport_data = {
                    'error': '',
                    'doc_type': 'passport'
                }

                try:
                    st = time.time()
                    processed_passport = self.image_conversion(passport)
                    logging.info(f'----------------Time taken for image conversion passport: {time.time() - st} seconds\n')

                    st = time.time()
                    id_infos = self.get_ocr_results(processed_passport)
                    passport_text = id_infos[0].description
                    logging.info(f'----------------Time taken for vision passport: {time.time() - st} seconds\n')

                    pattern4 = r'(Republic of Lebanon|Passport|Républeue Libanaise)'
                    k= re.search(pattern4, passport_text, re.IGNORECASE)

                    if not k:
                        passport_data["error"] = "not_passport"
                        return passport_data

                    # Check for Civil status document keywords in translated text
                    civil_status_keywords = r'(General Directorate|Convergence|Municipalities|Personal Status|statement of individuals)'
                    civil_status_match = re.search(civil_status_keywords, passport_text, re.IGNORECASE)
                    
                    if civil_status_match:
                        passport_data["error"] = "not_passport"
                        return passport_data
                    
                    # Check for blur using the new comprehensive method
                    image = np.array(processed_passport)
                    if is_image_blur(image, laplace_threshold=30, canny_threshold=1500, checks_threshold=120,
                                     min_area_percentage=1.0):
                        print(f"Blur/Brightness issue detected in front image, marking as covered photo")
                        passport_data['error'] = 'blur_photo'
                        return passport_data

                    st = time.time()
                    passport_details = lebanon_passport_extraction(passport_text)
                    logging.info(f'----------------Time taken for data formatting and final extraction passport: {time.time() - st} seconds\n')

                    passport_data.update(passport_details)

                    if passport_data['id_number'][:2]=='PR':
                        passport_data['error'] = 'not_allowed'
                        return passport_data

                    image = np.array(processed_passport)

                    st = time.time()
                    ## TODO: doc_on_pp and detect_photo_on_screen for LBN
                    # doc_on_pp_result = document_on_printed_paper(image)
                    doc_on_pp_result = 'clear'
                    screenshot_result = detect_screenshot(self.client, passport)
                    # photo_on_screen_result = detect_photo_on_screen(self.client, passport)
                    photo_on_screen_result = 'clear'
                    blurred, glare = self.get_blurred_and_glared_for_doc(image)
                    print(f"Nationality: {passport_details.get('nationality')}")
                    valid_nationality_result = self.check_nationality_in_iso_list(passport_details.get('nationality'))
                    logging.info(f'----------------Time taken for fraud detection attributes passport: {time.time() - st} seconds\n')

                    st = time.time()
                    front_face_locations, front_face_encodings = load_and_process_image_deepface(passport)
                    logging.info(f'----------------Time taken for face extraction passport: {time.time() - st} seconds\n')

                    front_face_locations_str = json.dumps([tuple(face_loc) for face_loc in front_face_locations])
                    front_face_encodings_str = json.dumps([face_enc.tolist() for face_enc in front_face_encodings])

                    passport_data_update = {
                            # 'back_tampered_result': tampered_result_back,
                            'passport_data': passport_text,
                            'front_coloured': True,
                            'back_coloured': True,
                            'front_logo_result': 'clear',
                            'front_doc_on_pp': doc_on_pp_result,
                            'front_screenshot_result': screenshot_result,
                            'front_photo_on_screen_result': photo_on_screen_result,
                            'doc_on_pp': doc_on_pp_result,
                            'screenshot_result': screenshot_result,
                            'photo_on_screen_result': photo_on_screen_result,
                            'front_blurred': blurred, 
                            'front_glare': glare,
                            'back_blurred': blurred, 
                            'back_glare': glare,
                            'front_face_locations': front_face_locations_str,
                            'front_face_encodings': front_face_encodings_str,
                            'valid_nationality': valid_nationality_result
                        }
                    
                    passport_data.update(passport_data_update)
                    
                    if not passport_data.get('id_number') or not passport_data.get('dob') or not passport_data.get('first_name') or not passport_data.get('last_name') or not passport_data.get('expiry_date'):
                        passport_data['error'] = 'covered_photo'

                    non_optional_keys = ["front_face_locations", "id_number", "dob", "first_name", "last_name", "expiry_date"]
                    empty_string_keys = [key for key, value in passport_data.items() if key in non_optional_keys and (value == '' or value == [] or value == '[]')]

                    if empty_string_keys:
                        passport_data['error'] = 'covered_photo'

                    if passport_data['mrz1']=='':
                        passport_data['error']='cropped_mrz'
                        passport_data['error_details'] = 'MRZ1 is null'

                    mrz2_pattern = r"^[A-Za-z]{2}\d{7}.*"

                    # Check if the extracted text matches the pattern
                    if not (re.match(mrz2_pattern, passport_data['mrz2'])):
                        passport_data['error'] = 'invalid mrz'
                        passport_data['error_details'] = 'MRZ not present in picture'

                    if len(passport_data['mrz2'])<43:
                        passport_data['error'] = 'cropped_mrz'
                        passport_data['error_details'] = 'MRZ not present in picture'

                    if not (re.match(mrz2_pattern, passport_data['id_number'])):
                        passport_data['error'] = 'id_number_not_found'
                        passport_data['error_details'] = 'ID Number not identified.'



                except Exception as e:
                    passport_data['error'] = 'bad_image'
                    passport_data['error_details'] = e


                return passport_data

        if country.upper() == 'SDN':
            logging.info("-------------Working on SDN Passport \n")
            from idvpackage.ocr_utils import detect_photo_on_screen, detect_screenshot, document_on_printed_paper
            from idvpackage.sudan_passport_extraction import sdn_passport_extraction
            from idvpackage.common import load_and_process_image_deepface
            from idvpackage.blur_detection import is_image_blur

            passport_data = {
                'error': '',
                'doc_type': 'passport'
            }

            try:
                st = time.time()
                processed_passport = self.image_conversion(passport)
                logging.info(f'----------------Time taken for image conversion passport: {time.time() - st} seconds\n')

                st = time.time()
                compressed_image = BytesIO()
                # TODO

                # processed_passport=self.preprocess_image(processed_passport, sharpness=2.0, contrast=1.5, radius=3, percent=180, threshold=5)
                # Enhance image sharpness and contrast.
                processed_passport.save(compressed_image, format="JPEG", quality=95, optimize=True)
                compressed_image_data = compressed_image.getvalue()
                id_infos = self.get_ocr_results(compressed_image_data, country='SDN')
                passport_text = id_infos[0].description
                logging.info(f'----------------Time taken for vision passport: {time.time() - st} seconds\n')

                pattern4 = r'(Republic of Sudan|Republic of the Sudan|PCSDN|SDN)'
                k = re.search(pattern4, passport_text, re.IGNORECASE)

                if k:
                    print(f"Keyword feature matches: {k.group()}\n")

                if not k:
                    passport_data["error"] = "not_passport"
                    return passport_data

                image = np.array(processed_passport)
                if is_image_blur(image):
                    print(f"Passport Document is blurry, marking as covered photo")
                    passport_data['error'] = 'blur_photo'
                    return passport_data

                st = time.time()
                passport_details = sdn_passport_extraction(passport_text)
                logging.info(
                    f'----------------Time taken for data formatting and final extraction passport: {time.time() - st} seconds\n')

                if passport_details.get('dob'):
                    try:
                        dob = passport_details['dob']
                        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 < 18:
                            passport_data['error'] = 'under_age'
                            return passport_data
                    except:
                        pass

                if passport_details.get('expiry_date'):
                    try:
                        expiry_date = passport_details['expiry_date']
                        expiry_date = datetime.strptime(expiry_date, "%d/%m/%Y")
                        current_date = datetime.now()

                        if expiry_date < current_date:
                            passport_data['error'] = 'expired_id'
                            return passport_data
                    except:
                        pass

                passport_data.update(passport_details)

                if passport_data.get('issue_date'):
                    passport_data['issuance_date'] = passport_details['issue_date']

                image = np.array(processed_passport)

                st = time.time()
                ## TODO: doc_on_pp and detect_photo_on_screen for LBN
                # doc_on_pp_result = document_on_printed_paper(image)
                doc_on_pp_result = 'clear'
                screenshot_result = detect_screenshot(self.client, passport)
                # photo_on_screen_result = detect_photo_on_screen(self.client, passport)
                photo_on_screen_result = 'clear'
                blurred, glare = self.get_blurred_and_glared_for_doc(image)
                valid_nationality_result = self.check_nationality_in_iso_list(passport_details.get('nationality'))
                logging.info(
                    f'----------------Time taken for fraud detection attributes passport: {time.time() - st} seconds\n')

                st = time.time()
                front_face_locations, front_face_encodings = load_and_process_image_deepface(passport, country='SDN')
                logging.info(f'----------------Time taken for face extraction passport: {time.time() - st} seconds\n')

                front_face_locations_str = json.dumps([tuple(face_loc) for face_loc in front_face_locations])
                front_face_encodings_str = json.dumps([face_enc.tolist() for face_enc in front_face_encodings])

                passport_data_update = {
                    # 'back_tampered_result': tampered_result_back,
                    'passport_data': passport_text,
                    'front_coloured': True,
                    'back_coloured': True,
                    'front_logo_result': 'clear',
                    'front_doc_on_pp': doc_on_pp_result,
                    'front_screenshot_result': screenshot_result,
                    'front_photo_on_screen_result': photo_on_screen_result,
                    'doc_on_pp': doc_on_pp_result,
                    'screenshot_result': screenshot_result,
                    'photo_on_screen_result': photo_on_screen_result,
                    'front_blurred': blurred,
                    'front_glare': glare,
                    'back_blurred': blurred,
                    'back_glare': glare,
                    'front_face_locations': front_face_locations_str,
                    'front_face_encodings': front_face_encodings_str,
                    'valid_nationality': valid_nationality_result
                }

                passport_data.update(passport_data_update)

                non_optional_keys = ["passport_number_mrz", "dob_mrz", "expiry_date_mrz", "gender"]
                empty_string_keys = [key for key, value in passport_data.items() if
                                     key in non_optional_keys and value == '']

                if empty_string_keys:
                    passport_data['error'] = 'missing_key_fields'

            except Exception as e:
                passport_data['error'] = 'Something went wrong'
                print(f"-------------->> Something went wrong error trace:: {e}")
                passport_data['error_details'] = e

            return passport_data
        
    def extract_ocr_info(self, data, video, country, report_names, back_img = None):
        st = time.time()
        from idvpackage.ocr_utils import form_final_data_document_report, form_final_facial_similarity_report 
        face_match_threshold=0.59   
        document_report = {}
        facial_report = {}

        if country=='IRQ':
            face_match_threshold = 0.70
        
        if country=='LBN':
            face_match_threshold = 0.50

        if country=='SDN':
            face_match_threshold=0.40

        tampering_result = 'clear'
        data['tampering_result'] = tampering_result

        if data.get('front_tampered_result')=='Tampered' or data.get('back_tampered_result')=='Tampered':
            tampering_result = 'consider'
            data['tampering_result'] = tampering_result

        if country == 'IRQ' and data.get('doc_type') == 'national_identity_card':
            if not data.get("gender") and data.get("gender_back"):
                data['gender'] = data.get("gender_back")

            # validation_result = self.validate_fields_id(data, country)
            # if not validation_result:
            #     tampering_result = 'consider'
            #     data['tampering_result'] = tampering_result
        
        colour_picture = 'consider'
        if data.get('front_coloured') and data.get('back_coloured'):
            colour_picture = 'clear'

        blurred = 'clear'
        if data.get('front_blurred')=='consider' or data.get('back_blurred')=='consider':
            blurred = 'consider'
        
        glare = 'clear'
        if data.get('front_glare')=='consider' or data.get('back_glare')=='consider':
            glare = 'consider'

        missing_fields = 'clear'
        if data.get('front_missing_fields') or data.get('back_missing_fields'):
            missing_fields = 'consider'


        if video:
            face_loc = json.loads(data.get('front_face_locations'))
            front_face_locations = tuple(face_loc)
            front_face_encodings = np.array(json.loads(data.get('front_face_encodings')))

            data['front_face_locations'] = front_face_locations
            data['front_face_encodings'] = front_face_encodings

            selfie_str = data.get('selfie')
            if isinstance(selfie_str, str):
                selfie = np.array(json.loads(selfie_str))
            else:
                selfie = selfie_str

            try:
                similarity = self.extract_face_and_compute_similarity(selfie, front_face_locations, front_face_encodings)
            except:
                print("issue in extracting face and computing similarity")
                selfie = None
                similarity = 0
            
        else:
            selfie = None
            similarity = 0

        # front_face_locations, front_face_encodings = data.get('front_face_locations'), data.get('front_face_encodings')
        # processed_selfie = self.process_image(selfie)
        if country == 'SAU' or data.get('doc_type') == 'passport':
            back_id_text = ''
        else:
            back_id_text = data.get('back_extracted_data')

        if data.get('doc_type') == 'national_identity_card':
            front_id_text = data.get('front_extracted_data')
        else:
            front_id_text = ''

        if 'document' in report_names:
            document_report = form_final_data_document_report(data, front_id_text, back_id_text, country, colour_picture, selfie, similarity, blurred, glare, missing_fields, face_match_threshold, back_img)

        if 'facial_similarity_video' in report_names:
            if video:
                liveness_result = self.check_for_liveness(similarity, video, face_match_threshold)
                # print(f"LIVE RES: {liveness_result}")
            else:
                liveness_result = None, None

            facial_report = form_final_facial_similarity_report(data, selfie, similarity, liveness_result, face_match_threshold, country)
        
        logging.info(f"--------------Time taken for Extract Ocr Function in IDV package: {time.time() - st} seconds\n")

        return document_report, facial_report
    
