"""Text extraction from PDF, DOCX, DOC, and RTF files.

Primary PDF extraction: PyMuPDF direct text (for born-digital PDFs).
Fallback: Google Cloud Vision OCR (for scanned documents).
DOC files: converted to DOCX via LibreOffice before extraction.
Post-processing: Hebrew abbreviation quote fixer.
"""

from __future__ import annotations

import asyncio
import logging
import re
import subprocess
import tempfile
from pathlib import Path

import fitz  # PyMuPDF
from docx import Document as DocxDocument
from google.cloud import vision
from striprtf.striprtf import rtf_to_text

from legal_mcp import config

logger = logging.getLogger(__name__)

# ── Google Cloud Vision client ───────────────────────────────────

_vision_client: vision.ImageAnnotatorClient | None = None


def _get_vision_client() -> vision.ImageAnnotatorClient:
    global _vision_client
    if _vision_client is None:
        _vision_client = vision.ImageAnnotatorClient(
            client_options={"api_key": config.GOOGLE_CLOUD_VISION_API_KEY}
        )
    return _vision_client


# ── Hebrew text quality detection ────────────────────────────────

_HEBREW_RE = re.compile(r'[\u0590-\u05FF]')
_WORD_RE = re.compile(r'\S+')


def _text_quality_ok(text: str) -> bool:
    """Check if extracted text is real content vs broken OCR layer.

    Returns True if text appears to be genuine Hebrew legal content.
    Broken OCR layers from scanned PDFs often have:
    - Very short words / single-character fragments
    - Each word on its own line (high words-per-line ratio)
    - Non-Hebrew characters mixed in
    """
    words = _WORD_RE.findall(text)
    if len(words) < 10:
        return False

    # Average word length — real Hebrew words avg 4-6 chars.
    avg_len = sum(len(w) for w in words) / len(words)
    if avg_len < 2.5:
        return False

    # Percentage of single-character "words"
    single_char_pct = sum(1 for w in words if len(w) == 1) / len(words)
    if single_char_pct > 0.4:
        return False

    # Words per line — broken OCR puts each word on its own line.
    # Real text has 5-15 words per line; broken OCR has ~1-2.
    lines = [l for l in text.split("\n") if l.strip()]
    if lines:
        words_per_line = len(words) / len(lines)
        if words_per_line < 3.0:
            return False

    # Hebrew character ratio among letter characters
    letters = re.findall(r'[a-zA-Z\u0590-\u05FF]', text)
    if letters:
        hebrew_pct = sum(1 for c in letters if _HEBREW_RE.match(c)) / len(letters)
        if hebrew_pct < 0.5:
            return False

    return True


# ── Hebrew abbreviation quote fixer ──────────────────────────────

_HEBREW_ABBREV_FIXES: dict[str, str] = {
    'עוהייד': 'עוה"ד',
    'עוייד': 'עו"ד',
    'הנייל': 'הנ"ל',
    'מצייב': 'מצ"ב',
    'ביהמייש': 'ביהמ"ש',
    'תייז': 'ת"ז',
    'עייי': 'ע"י',
    'אחייכ': 'אח"כ',
    'סייק': 'ס"ק',
    'דייר': 'ד"ר',
    'כדוייח': 'כדו"ח',
    'חווייד': 'חוו"ד',
    'מייר': 'מ"ר',
    'יחייד': 'יח"ד',
    'בייכ': 'ב"כ',
}

_ABBREV_PATTERN = re.compile(
    '|'.join(re.escape(k) for k in sorted(_HEBREW_ABBREV_FIXES, key=len, reverse=True))
)


def _fix_hebrew_quotes(text: str) -> str:
    """Fix known Hebrew abbreviation quote replacements from Google Vision OCR."""
    return _ABBREV_PATTERN.sub(lambda m: _HEBREW_ABBREV_FIXES[m.group()], text)


# ── Extraction ───────────────────────────────────────────────────


async def extract_text(file_path: str) -> tuple[str, int]:
    """Extract text from a document file.

    Returns:
        Tuple of (extracted_text, page_count).
        page_count is 0 for non-PDF files.
    """
    path = Path(file_path)
    suffix = path.suffix.lower()

    if suffix == ".pdf":
        return await _extract_pdf(path)
    elif suffix == ".docx":
        return _extract_docx(path), 0
    elif suffix == ".doc":
        return _extract_doc(path), 0
    elif suffix == ".rtf":
        return _extract_rtf(path), 0
    elif suffix in (".txt", ".md"):
        return path.read_text(encoding="utf-8"), 0
    else:
        raise ValueError(f"Unsupported file type: {suffix}")


async def _extract_pdf(path: Path) -> tuple[str, int]:
    """Extract text from PDF.

    Try direct text first, fall back to Google Cloud Vision for scanned
    or broken-OCR pages.
    """
    doc = fitz.open(str(path))
    page_count = len(doc)
    pages_text: list[str] = []

    for page_num in range(page_count):
        page = doc[page_num]
        text = page.get_text().strip()

        if len(text) > 50 and _text_quality_ok(text):
            pages_text.append(text)
            logger.debug("Page %d: direct extraction (%d chars, quality OK)", page_num + 1, len(text))
        else:
            reason = "insufficient text" if len(text) <= 50 else "low quality OCR layer"
            logger.info("Page %d: Google Vision OCR (%s)", page_num + 1, reason)
            pix = page.get_pixmap(dpi=300)
            img_bytes = pix.tobytes("png")
            ocr_text = await asyncio.to_thread(
                _ocr_with_google_vision, img_bytes, page_num + 1
            )
            pages_text.append(ocr_text)

    doc.close()
    return "\n\n".join(pages_text), page_count


def _ocr_with_google_vision(image_bytes: bytes, page_num: int) -> str:
    """OCR a single page image using Google Cloud Vision API."""
    client = _get_vision_client()
    image = vision.Image(content=image_bytes)

    response = client.document_text_detection(
        image=image,
        image_context=vision.ImageContext(language_hints=["he"]),
    )

    if response.error.message:
        raise RuntimeError(
            f"Google Vision error on page {page_num}: {response.error.message}"
        )

    text = response.full_text_annotation.text if response.full_text_annotation else ""
    return _fix_hebrew_quotes(text)


def _extract_doc(path: Path) -> str:
    """Extract text from legacy .doc file by converting to .docx via LibreOffice."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        result = subprocess.run(
            ["libreoffice", "--headless", "--convert-to", "docx", str(path), "--outdir", tmp_dir],
            capture_output=True, text=True, timeout=120,
        )
        if result.returncode != 0:
            raise RuntimeError(f"LibreOffice conversion failed: {result.stderr}")
        docx_path = Path(tmp_dir) / f"{path.stem}.docx"
        if not docx_path.exists():
            raise FileNotFoundError(f"Converted file not found: {docx_path}")
        return _extract_docx(docx_path)


def _extract_docx(path: Path) -> str:
    """Extract text from DOCX file."""
    doc = DocxDocument(str(path))
    paragraphs = [p.text for p in doc.paragraphs if p.text.strip()]
    return "\n\n".join(paragraphs)


def _extract_rtf(path: Path) -> str:
    """Extract text from RTF file."""
    rtf_content = path.read_text(encoding="utf-8", errors="replace")
    return rtf_to_text(rtf_content)


# ── Nevo preamble stripping ──────────────────────────────────────

_NEVO_MARKERS = ("ספרות:", "חקיקה שאוזכרה:", "מיני-רציו:", "פסקי דין שאוזכרו:",
                 "כתבי עת:", "הועתק מנבו")

_DECISION_START = re.compile(
    r"^(בפנינו|לפנינו|הערר שבנדון|ועדת הערר לתכנון|רקע עובדתי|עסקינן)",
    re.MULTILINE,
)


def strip_nevo_preamble(text: str) -> str:
    """Remove Nevo database preamble (bibliography, legislation, mini-ratio) from decision text.

    Returns the original text unchanged if no preamble is detected.
    """
    head = text[:400]
    if not any(marker in head for marker in _NEVO_MARKERS):
        return text
    m = _DECISION_START.search(text)
    if m and m.start() > 50:
        stripped = text[m.start():]
        logger.debug("Stripped %d chars of Nevo preamble", m.start())
        return stripped
    return text
