import os

# -----------------------------------
# FIX HOME ERROR
# -----------------------------------

os.environ["HOME"] = "/home/online25"

# -----------------------------------
# HIDE STDERR
# -----------------------------------

import sys

sys.stderr = open(os.devnull, "w")

# -----------------------------------
# LOW RAM FIX
# -----------------------------------

os.environ["OPENBLAS_NUM_THREADS"] = "1"
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["MKL_NUM_THREADS"] = "1"
os.environ["VECLIB_MAXIMUM_THREADS"] = "1"
os.environ["NUMEXPR_NUM_THREADS"] = "1"

# -----------------------------------
# IMPORTS
# -----------------------------------

import fitz
import cv2
import numpy as np
import json
import re
import uuid
import gc

# -----------------------------------
# CONFIG
# -----------------------------------

BASE_URL = "https://sign.onlineshopbd.top/uploads/images/"
SAVE_DIR = "/home/online25/sign.onlineshopbd.top/uploads/images"

os.makedirs(SAVE_DIR, exist_ok=True)

try:

    # -----------------------------------
    # CHECK PDF
    # -----------------------------------

    if len(sys.argv) < 2:

        print(json.dumps({
            "success": False,
            "message": "PDF path missing"
        }))

        sys.exit()

    pdf_path = sys.argv[1]

    # -----------------------------------
    # OPEN PDF
    # -----------------------------------

    doc = fitz.open(pdf_path)

    page = doc[0]

    # -----------------------------------
    # PDF TEXT
    # -----------------------------------

    pdf_text = page.get_text()

    clean_text = re.sub(
        r'\s+',
        ' ',
        pdf_text
    )

    # -----------------------------------
    # SAFE LOW RAM PDF RENDER
    # -----------------------------------

    try:

        render_scale = 2

        pix = page.get_pixmap(
            matrix=fitz.Matrix(
                render_scale,
                render_scale
            )
        )

    except:

        render_scale = 1.5

        pix = page.get_pixmap(
            matrix=fitz.Matrix(
                render_scale,
                render_scale
            )
        )

    img = np.frombuffer(
        pix.samples,
        dtype=np.uint8
    ).reshape(
        pix.height,
        pix.width,
        pix.n
    )

    del pix
    gc.collect()

    if img.shape[2] == 4:

        img = cv2.cvtColor(
            img,
            cv2.COLOR_RGBA2RGB
        )

    original = img.copy()

    # -----------------------------------
    # GET VALUE
    # -----------------------------------

    def get_value(label, next_label=None):

        try:

            if next_label:

                pattern = rf"{label}(.*?){next_label}"

            else:

                pattern = rf"{label}(.*)"

            match = re.search(
                pattern,
                clean_text,
                re.IGNORECASE
            )

            if match:

                value = match.group(1).strip()

                value = re.sub(
                    r'\s+',
                    ' ',
                    value
                )

                return value

            return ""

        except:

            return ""

    # -----------------------------------
    # CLEAN BANGLA NAME
    # -----------------------------------

    def clean_bangla_name(name):

        try:

            # Fix pattern 1: মোঃাঃ → মোঃ
            name = re.sub('\u0983\u09be\u0983', '\u0983', name)

            # Fix pattern 2: মোঃঃ → মোঃ
            name = re.sub('\u0983\u0983', '\u0983', name)

            # extra space fix
            name = re.sub(r'\s+', ' ', name).strip()

            return name

        except:

            return name

    # -----------------------------------
    # SAFE RGB TO BGR
    # -----------------------------------

    def safe_bgr(image):

        try:

            if image is None:
                return None

            if len(image.shape) == 2:

                return image

            return cv2.cvtColor(
                image,
                cv2.COLOR_RGB2BGR
            )

        except:

            return image

    # -----------------------------------
    # EXACT BOX CROP
    # -----------------------------------

    def exact_crop(image):

        try:

            gray = cv2.cvtColor(
                image,
                cv2.COLOR_RGB2GRAY
            )

            _, th = cv2.threshold(
                gray,
                245,
                255,
                cv2.THRESH_BINARY_INV
            )

            coords = cv2.findNonZero(th)

            if coords is None:
                return image

            x, y, w, h = cv2.boundingRect(coords)

            cropped = image[
                y:y+h,
                x:x+w
            ]

            return cropped

        except:

            return image

    # -----------------------------------
    # IS VALID SIGNATURE
    # -----------------------------------

    def is_valid_signature(img):

        try:

            if img is None:
                return False

            h, w = img.shape[:2]

            # too small
            if w < 40 or h < 15:
                return False

            # too large (likely wrong crop)
            if w > 600 or h > 300:
                return False

            # -----------------------------------
            # REJECT ORANGE (Smart Card button)
            # -----------------------------------

            r = img[:, :, 0]
            g = img[:, :, 1]
            b = img[:, :, 2]

            orange_pixels = np.sum(
                (r > 180) &
                (g > 80) &
                (g < 160) &
                (b < 80)
            )

            if orange_pixels > 100:
                return False

            # -----------------------------------
            # REJECT BLUE (links/buttons)
            # -----------------------------------

            blue_pixels = np.sum(
                (b > 150) &
                (r < 100) &
                (g < 150)
            )

            if blue_pixels > 200:
                return False

            # -----------------------------------
            # CHECK DARK PIXEL RATIO
            # -----------------------------------

            gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

            _, th = cv2.threshold(
                gray, 180, 255,
                cv2.THRESH_BINARY_INV
            )

            dark_pixels = cv2.countNonZero(th)
            total_pixels = w * h
            ratio = dark_pixels / total_pixels

            # signature must have some dark content
            if ratio < 0.01 or ratio > 0.60:
                return False

            return True

        except:

            return False

    # -----------------------------------
    # DATA
    # -----------------------------------

    data = {

        "nameBangla":
        clean_bangla_name(
            get_value(
                "Name\\(Bangla\\)",
                "Name\\(English\\)"
            )
        ),

        "nameEnglish":
        get_value(
            "Name\\(English\\)",
            "Date of Birth"
        ),

        "nationalId":
        get_value(
            "National ID",
            "Pin"
        ),

        "pin":
        get_value(
            "Pin",
            "Status"
        ),

        "dateOfBirth":
        get_value(
            "Date of Birth",
            "Birth Place"
        ),

        "dateOfToday":
        "",

        "fatherName":
        clean_bangla_name(
            get_value(
                "Father Name",
                "Mother Name"
            )
        ),

        "motherName":
        clean_bangla_name(
            get_value(
                "Mother Name",
                "Spouse Name"
            )
        ),

        "gender":
        get_value(
            "Gender",
            "Marital"
        ).upper(),

        "religion":
        get_value(
            "Religion",
            "Religion Other"
        ),

        "birthPlace":
        get_value(
            "Birth Place",
            "Birth Other"
        ),

        "bloodGroup":
        get_value(
            "Blood Group",
            "TIN"
        )
    }

    # -----------------------------------
    # ADDRESS FORMAT FIX
    # -----------------------------------

    def extract_between(start, end_list, text):

        try:

            pattern = rf"{start}(.*?)({'|'.join(end_list)})"

            match = re.search(
                pattern,
                text,
                re.IGNORECASE
            )

            if match:

                value = match.group(1).strip()

                value = re.sub(
                    r'\s+',
                    ' ',
                    value
                ).strip()

                return value

            return ""

        except:

            return ""

    # -----------------------------------
    # ADDRESS VALUE EXTRACT
    # -----------------------------------

    home = extract_between(
        r'Home/Holding No',
        [
            'Post Office',
            'Postal Code'
        ],
        clean_text
    )

    village = extract_between(
        r'Additional Village/Road',
        [
            'Home/Holding No',
            'Post Office',
            'Postal Code'
        ],
        clean_text
    )

    if not village:

        village = extract_between(
            r'Village/Road',
            [
                'Home/Holding No',
                'Post Office',
                'Postal Code'
            ],
            clean_text
        )

    post = extract_between(
        r'Post Office',
        [
            'Postal Code',
            'Region'
        ],
        clean_text
    )

    postal = extract_between(
        r'Postal Code',
        [
            'Region',
            'Permanent Address'
        ],
        clean_text
    )

    upozila = extract_between(
        r'Upozila',
        [
            'Union/Ward',
            'Mouza/Moholla'
        ],
        clean_text
    )

    district = extract_between(
        r'District',
        [
            'RMO',
            'City Corporation'
        ],
        clean_text
    )

    # -----------------------------------
    # CLEAN ADDRESS VALUE
    # -----------------------------------

    def clean_address_value(v):

        try:

            if not v:
                return ""

            bad_words = [

                "Additional",
                "Village/Road",
                "Home/Holding",
                "Post Office",
                "Postal Code",
                "Region",
                "Permanent Address",
                "Foreign Address",
                "RMO",
                "City Corporation",
                "Municipality",
                "Union/Ward",
                "Mouza/Moholla"
            ]

            for b in bad_words:

                v = re.sub(
                    rf'{b}.*',
                    '',
                    v,
                    flags=re.IGNORECASE
                )

            v = re.sub(
                r'\s+',
                ' ',
                v
            ).strip()

            return v

        except:

            return ""

    home = clean_address_value(home)
    village = clean_address_value(village)
    post = clean_address_value(post)
    postal = clean_address_value(postal)
    upozila = clean_address_value(upozila)
    district = clean_address_value(district)

    # -----------------------------------
    # FINAL ADDRESS
    # -----------------------------------

    address = (
        f"বাসা/হোল্ডিং: {home if home else '-'}, "
        f"গ্রাম/রাস্তা: {village}, "
        f"ডাকঘর: {post} - {postal}, "
        f"{upozila}, "
        f"{district}"
    )

    address = re.sub(
        r'\s+',
        ' ',
        address
    ).strip()

    data["address"] = address

    # -----------------------------------
    # RIGHT SIDE AREA
    # -----------------------------------

    h, w = original.shape[:2]

    right = original[
        :,
        int(w * 0.68):
    ]

    # -----------------------------------
    # LOW RAM RESIZE
    # -----------------------------------

    max_right_width = 1000

    if right.shape[1] > max_right_width:

        ratio_resize = (
            max_right_width / right.shape[1]
        )

        new_h = int(
            right.shape[0] * ratio_resize
        )

        right = cv2.resize(
            right,
            (max_right_width, new_h)
        )

    gc.collect()

    # -----------------------------------
    # PERSON DETECTION
    # -----------------------------------

    gray = cv2.cvtColor(
        right,
        cv2.COLOR_RGB2GRAY
    )

    blur = cv2.GaussianBlur(
        gray,
        (5,5),
        0
    )

    _, thresh = cv2.threshold(
        blur,
        180,
        255,
        cv2.THRESH_BINARY_INV
    )

    kernel = cv2.getStructuringElement(
        cv2.MORPH_RECT,
        (5,5)
    )

    thresh = cv2.dilate(
        thresh,
        kernel,
        iterations=1
    )

    contours, _ = cv2.findContours(
        thresh,
        cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE
    )

    del thresh
    gc.collect()

    person = None
    signature = None

    best_person = None
    best_person_area = 0

    # -----------------------------------
    # FIND PERSON
    # -----------------------------------

    for cnt in contours:

        x, y, cw, ch = cv2.boundingRect(cnt)

        area = cw * ch

        if area < 12000:
            continue

        ratio = cw / float(ch)

        if 0.45 <= ratio <= 1.10:

            if y < right.shape[0] * 0.70:

                if area > best_person_area:

                    best_person_area = area

                    best_person = (
                        x,
                        y,
                        cw,
                        ch
                    )

    # -----------------------------------
    # PERSON FOUND
    # -----------------------------------

    if best_person:

        px, py, pw, ph = best_person

        person = right[
            py:py+ph,
            px:px+pw
        ].copy()

        person = exact_crop(person)

        lx1 = max(px - 100, 0)

        lx2 = min(
            px + pw + 100,
            right.shape[1]
        )

        lower = right[
            py+ph:right.shape[0],
            lx1:lx2
        ].copy()

        max_width = 450

        if lower.shape[1] > max_width:

            ratio_resize = (
                max_width / lower.shape[1]
            )

            new_h = int(
                lower.shape[0] * ratio_resize
            )

            lower = cv2.resize(
                lower,
                (max_width, new_h)
            )

        gc.collect()

        if lower.shape[0] > 0 and lower.shape[1] > 0:

            lgray = cv2.cvtColor(
                lower,
                cv2.COLOR_RGB2GRAY
            )

            lgray = cv2.GaussianBlur(
                lgray,
                (3,3),
                0
            )

            _, th1 = cv2.threshold(
                lgray,
                220,
                255,
                cv2.THRESH_BINARY_INV
            )

            kernel1 = cv2.getStructuringElement(
                cv2.MORPH_RECT,
                (5,5)
            )

            th1 = cv2.dilate(
                th1,
                kernel1,
                iterations=1
            )

            cnts1, _ = cv2.findContours(
                th1,
                cv2.RETR_EXTERNAL,
                cv2.CHAIN_APPROX_SIMPLE
            )

            del th1
            gc.collect()

            best_sign = None
            best_score = 0

            for c in cnts1:

                sx, sy, sw, sh = cv2.boundingRect(c)

                area = sw * sh

                if area < 1200:
                    continue

                ratio = sw / float(sh)

                score = area

                if 0.50 <= ratio <= 1.60:

                    score += 7000

                elif ratio > 1.80:

                    score += 9000

                else:

                    continue

                if sy > lower.shape[0] * 0.05:

                    score += 3000

                if sw > 60:

                    score += 2000

                if score > best_score:

                    best_score = score

                    best_sign = (
                        sx,
                        sy,
                        sw,
                        sh
                    )

            # -----------------------------------
            # SIGNATURE FOUND
            # -----------------------------------

            if best_sign:

                sx, sy, sw, sh = best_sign

                pad_x = 16
                pad_y = 12

                sx1 = max(sx - pad_x, 0)
                sy1 = max(sy - pad_y, 0)

                sx2 = min(
                    sx + sw + pad_x,
                    lower.shape[1]
                )

                sy2 = min(
                    sy + sh + pad_y,
                    lower.shape[0]
                )

                sign_crop = lower[
                    sy1:sy2,
                    sx1:sx2
                ].copy()

                sign_crop = exact_crop(sign_crop)

                # validate before saving
                if is_valid_signature(sign_crop):

                    signature = sign_crop

    # -----------------------------------
    # SAVE PERSON
    # -----------------------------------

    userIMG = ""

    if person is not None:

        person_name = (
            f"user_{uuid.uuid4().hex}.jpg"
        )

        person_path = os.path.join(
            SAVE_DIR,
            person_name
        )

        cv2.imwrite(
            person_path,
            safe_bgr(person),
            [
                cv2.IMWRITE_JPEG_QUALITY,
                60
            ]
        )

        userIMG = BASE_URL + person_name

    # -----------------------------------
    # SAVE SIGNATURE
    # -----------------------------------

    signIMG = ""

    if signature is not None:

        sign_name = (
            f"sign_{uuid.uuid4().hex}.jpg"
        )

        sign_path = os.path.join(
            SAVE_DIR,
            sign_name
        )

        cv2.imwrite(
            sign_path,
            safe_bgr(signature),
            [
                cv2.IMWRITE_JPEG_QUALITY,
                60
            ]
        )

        signIMG = BASE_URL + sign_name

    # -----------------------------------
    # ADD IMAGE URL
    # -----------------------------------

    data["userIMG"] = userIMG
    data["signIMG"] = signIMG

    # -----------------------------------
    # CLEAN MEMORY
    # -----------------------------------

    del img
    del original
    del right

    gc.collect()

    # -----------------------------------
    # RESPONSE
    # -----------------------------------

    response = {

        "success": True,

        "data": data
    }

    print(
        json.dumps(
            response,
            ensure_ascii=False
        )
    )

# -----------------------------------
# ERROR
# -----------------------------------

except Exception as e:

    gc.collect()

    print(
        json.dumps({
            "success": False,
            "error": str(e)
        })
    )