"""
Pure-Python PDF text extractor for Turkish e-Nabız blood work reports.

Replaces the old OCR pipeline (pdftoppm + tesseract + shell script) with
a single `pdfplumber.open()` call.  No system packages required — just
`pip install pdfplumber`.
"""

import re
import uuid
from datetime import UTC, datetime
from typing import Optional


def _parse_turkish_float(s: str) -> Optional[float]:
    """Parse a number string that may use comma as decimal separator."""
    if not s:
        return None
    s_clean = s.strip()
    if s_clean.startswith("(") and s_clean.endswith(")"):
        s_clean = s_clean[1:-1].strip()
    if s_clean.startswith("+"):
        s_clean = s_clean[1:].strip()
        
    try:
        return float(s_clean.replace(",", "."))
    except ValueError:
        return None


def _extract_hospital(lines: list[str]) -> str:
    """Try to pull the hospital / healthcare facility name."""
    for line in lines:
        if "Healthcare Facility:" in line:
            return line.split("Healthcare Facility:", 1)[1].strip()
        # Turkish variant
        if "Sağlık Tesisi:" in line or "Saglik Tesisi:" in line:
            return line.split(":", 1)[1].strip()
    return "Unknown Hospital"


def _extract_date(lines: list[str]) -> Optional[str]:
    """Try to extract the test date from the header lines."""
    for line in lines:
        # Look for "Date: DD.MM.YYYY" or "Tarih: DD.MM.YYYY"
        m = re.search(r'(?:Date|Tarih)\s*:\s*(\d{2}\.\d{2}\.\d{4})', line)
        if m:
            parts = m.group(1).split(".")
            return f"{parts[2]}-{parts[1]}-{parts[0]}"
    return None


# Regex for a typical result row.  Matches patterns like:
#   (v) Alanin aminotransferaz (ALT) 12 U/L 0-55
#   V] Anti HBs 65,07 1U/ml
#   Ferritin 28,52 ug/L 21,81-274,66
_ROW_RE = re.compile(
    r"""
    ^                                           # start of line
    (?:\([v1i!]\)|\[?[v1i!]\]?)?                # optional status marker like (v), v], [v], (1), etc.
    \s*                                         # optional whitespace
    ([A-Za-zÀ-ÿ\s\(\)#%/\-'ğüşöçİĞÜŞÖÇ]+?)   # test name  (group 1)
    \s+                                         # separator
    (\d+(?:[.,]\d+)?)                           # result value  (group 2)
    \s+                                         # separator
    ([A-Za-z0-9/%#.\-]+(?:/[A-Za-z0-9]+)*)     # unit  (group 3)
    (?:\s+(.+))?                                # optional reference range  (group 4)
    $
    """,
    re.VERBOSE,
)

# Regex for standalone limit lines like "< 1,00" or "> 35"
_LIMIT_RE = re.compile(r'^([<>])\s*(\d+(?:[.,]\d+)?)')

# Regex for a range like "0-55" or "21,81-274,66"
_RANGE_RE = re.compile(r'\(?[+-]?(\d+(?:[.,]\d+)?)\)?\s*[-–—−]\s*\(?[+-]?(\d+(?:[.,]\d+)?)\)?')

# Regex for a one-sided limit in the ref column, e.g. "< 200" or "> 35"
_REF_LIMIT_RE = re.compile(r'([<>\u2264\u2265≤≥=])\s*\(?[+-]?(\d+(?:[.,]\d+)?)\)?')


def _parse_ref_range(raw: Optional[str]) -> tuple[Optional[float], Optional[float]]:
    """Parse a reference range string into (min, max)."""
    if not raw:
        return None, None

    raw = raw.strip().rstrip("-–—−~")

    # Try range first: "0-55", "21,81-274,66"
    m = _RANGE_RE.search(raw)
    if m:
        return _parse_turkish_float(m.group(1)), _parse_turkish_float(m.group(2))

    # Try one-sided limit: "<200", "> 35"
    m = _REF_LIMIT_RE.search(raw)
    if m:
        op = m.group(1)
        val = _parse_turkish_float(m.group(2))
        if op in ("<", "≤", "\u2264"):
            return None, val
        elif op in (">", "≥", "\u2265"):
            return val, None
        elif op == "=":
            return val, val

    # Just a number remaining? (e.g., "(+0)" with trailing dash stripped)
    single_val = _parse_turkish_float(raw)
    if single_val is not None:
        return None, single_val

    return None, None


def parse_blood_work_text(full_text: str) -> dict:
    """
    Parse blood-work text (extracted from PDF pages) into a structured dict
    matching the shape expected by the /tests/json endpoint.
    """
    lines = full_text.splitlines()
    hospital = _extract_hospital(lines)
    test_date = _extract_date(lines)

    subtests: list[dict] = []

    # Lines to skip (footers, headers, noise, and reference threshold descriptions)
    _SKIP_RE = re.compile(
        r'^(Page\s+\d|Sayfa\s+\d|enabiz\.gov\.tr|0\s*850|Kişisel\s+Sağ|Kisisel\s+Sag|'
        r'T\.C\.\s*SAĞ|T\.C\.\s*SAG|Sağlık\s+Bilgi|Saglik\s+Bilgi|'
        r'Name/|Date\s+Test|Result\s+Referenc|Unit\s+e\s+Value|'
        r'Healthcare\s+Facility|Date:|Gender:|\d{2}\.\d{2}\.\d{4}$|'
        r'Sağlık\s+Tesisi|Saglik\s+Tesisi)|'
        r'(NEGAT[Iİı1]F|POZ[Iİı1]T[Iİı1]F|NEGATIVE|POSITIVE|^\s*=|'
        r'Trimester|Gebelerde|Haftalık|Aylık|Yaş\s*arası|Erkeklerde|Kadınlarda|Kızlarda)',
        re.IGNORECASE
    )

    # Optional reference range at the end of the line
    # Matches: " 4 - 6.2", " 12-15", " < 1.00-", " > 35", " ≤ 5", " (+0)-"
    ref_range_re = re.compile(r"""
        (?:
            \s+
            (?:
                (?:[<>\u2264\u2265≤≥=]\s*)?\(?[+-]?\d+(?:[.,]\d+)?\)?\s*[-–—−~]\s*\(?[+-]?\d+(?:[.,]\d+)?\)?\s*[a-zA-Z]*[-~]?  # Range
                |
                [<>\u2264\u2265≤≥=]\s*\(?[+-]?\d+(?:[.,]\d+)?\)?\s*[-–—−~]* # Single limit with symbol
                |
                \(?[+-]?\d+(?:[.,]\d+)?\)?\s*[-–—−~]+ # Single limit with trailing dash (e.g. "35-", "(+0)-")
            )
        )$
    """, re.VERBOSE)

    # To identify if a token is a pure number (the result value)
    num_re = re.compile(r'^\(?[+-]?\d+(?:[.,]\d+)?\)?$')

    # Optional status markers to strip from the beginning
    marker_re = re.compile(r'^(?:\([vi1!I]\)|\[[vi1!I]\]|\u2713|\u2714|\u2717|\u2718|•|-)\s*')

    for i, raw_line in enumerate(lines):
        line = raw_line.strip()
        if not line:
            continue

        # Skip known noise lines
        if _SKIP_RE.search(line):
            continue

        # Handle standalone limit lines that modify the previous subtest
        limit_match = _LIMIT_RE.match(line)
        if limit_match and subtests:
            op = limit_match.group(1)
            val = _parse_turkish_float(limit_match.group(2))
            if val is not None:
                last = subtests[-1]
                if op == "<":
                    last["refMax"] = val
                elif op == ">":
                    last["refMin"] = val
            continue

        # Strip marker from beginning
        line = marker_re.sub('', line).strip()
        
        # 1. Extract and remove reference range from the end
        ref_min = None
        ref_max = None
        ref_match = ref_range_re.search(line)
        if ref_match:
            ref_raw = ref_match.group(0).strip()
            line = line[:ref_match.start()].strip()
            ref_min, ref_max = _parse_ref_range(ref_raw)
            
        # 2. Tokenize the remaining line
        tokens = line.split()
        
        # 3. Find the right-most pure number as the Value
        val_idx = -1
        for j in range(len(tokens) - 1, -1, -1):
            if num_re.match(tokens[j]):
                val_idx = j
                break
                
        if val_idx == -1 or val_idx == 0:
            # Line has no valid value, or value is the very first token (no name)
            continue
            
        # 4. Extract parts
        name = " ".join(tokens[:val_idx]).strip()
        value = _parse_turkish_float(tokens[val_idx])
        unit = " ".join(tokens[val_idx+1:]) if val_idx + 1 < len(tokens) else ""
        
        subtests.append({
            "name": name,
            "value": value,
            "unit": unit,
            "refMin": ref_min,
            "refMax": ref_max,
        })

    return {
        "testId": str(uuid.uuid4()),
        "hospitalName": hospital,
        "date": test_date or datetime.now(UTC).strftime("%Y-%m-%d"),
        "subtests": subtests,
    }


def parse_pdf_file(file_path: str) -> dict:
    """
    Open a PDF with pdfplumber, extract all page text, and parse it.
    """
    import pdfplumber

    pages_text: list[str] = []
    with pdfplumber.open(file_path) as pdf:
        for page in pdf.pages:
            text = page.extract_text()
            if text:
                pages_text.append(text)

    full_text = "\n\n".join(pages_text)
    return parse_blood_work_text(full_text)


def parse_pdf_bytes(file_bytes, filename: str = "upload.pdf") -> dict:
    """
    Parse a PDF from an in-memory file-like object (e.g. Flask's FileStorage).
    """
    import pdfplumber
    import io

    if isinstance(file_bytes, bytes):
        file_bytes = io.BytesIO(file_bytes)

    pages_text: list[str] = []
    with pdfplumber.open(file_bytes) as pdf:
        for page in pdf.pages:
            text = page.extract_text()
            if text:
                pages_text.append(text)

    full_text = "\n\n".join(pages_text)
    return parse_blood_work_text(full_text)
