import os
import json
from pathlib import Path
from bridge_platform.ai.openai_client import run_llm
from apps.wp_invoices.services.ocr_layout import extract_layout, transcript

BASE = Path(__file__).resolve().parents[1]
PROMPT_DETECT = BASE / "prompts" / "detect_type.v1.md"
PROMPT_POS = BASE / "prompts" / "extractor.pos.v1.2.md"
PROMPT_CORP = BASE / "prompts" / "extractor.corporate.v1.2.md"
PROMPT_UNIFIED_V2 = BASE / "prompts" / "extractor.unified.v2.md"
SCHEMA_UNIFIED_V2 = BASE / "contracts" / "extraction.unified.v2.schema.json"
PROMPT_ITEMS_RECOVERY_V2 = BASE / "prompts" / "items-recovery.v2.md"
SCHEMA_ITEMS_RECOVERY_V2 = BASE / "contracts" / "items-recovery.v2.schema.json"

MODEL_MAP = {
    "mini": os.getenv("OPENAI_MODEL_MINI", "gpt-4o-mini"),
    "thinking": os.getenv("OPENAI_MODEL_THINKING", "gpt-5-thinking"),
}


def _load(path: Path) -> str:
    with open(path, "r", encoding="utf-8") as f:
        return f.read()


def detect_type(file_bytes: bytes, filename: str, mimetype: str, context=None) -> tuple[str, float]:
    prompt = _load(PROMPT_DETECT)
    print("🟡 detect_type: llamando a run_llm con modelo mini")
    res = run_llm(
        context=context,
        app_id="wp_invoices",
        prompt=prompt,
        model=MODEL_MAP["mini"],
        options={
            "file_bytes": file_bytes,
            "filename": filename,
            "mimetype": mimetype,
            "response_format": "json_object",
        },
    )
    res = res.get("output")
    print("🟡 detect_type: respuesta recibida")

    if isinstance(res, str):
        res = json.loads(res)

    itype = str(res.get("invoice_type", "")).upper()
    conf = float(res.get("confidence") or 0.0)

    if itype not in ("POS", "CORPORATE"):
        # fallback rápido
        itype = "POS"

    print(f"🟡 detect_type: tipo={itype}, confidence={conf}")
    return itype, conf


def run_extraction(*, data: bytes, filename: str, mimetype: str, engine: str, context=None):
    print("🟢 run_extraction: inicio")

    # 1) Detectar tipo
    try:
        itype, conf = detect_type(data, filename, mimetype, context=context)
    except Exception as e:
        print(f"🔴 detect_type falló: {e}")
        # fallback duro: asumimos POS
        itype, conf = "POS", 0.0

    # 2) Elegir prompt según tipo
    if itype == "CORPORATE":
        prompt_path = PROMPT_CORP
    else:
        prompt_path = PROMPT_POS

    prompt = _load(prompt_path)
    model = MODEL_MAP.get(engine, MODEL_MAP["mini"])

    print(f"🟢 run_extraction: usando modelo={model}, tipo={itype}, prompt={prompt_path.name}")

    # 3) Llamar extractor principal
    res = run_llm(
        context=context,
        app_id="wp_invoices",
        prompt=prompt,
        model=model,
        options={
            "file_bytes": data,
            "filename": filename,
            "mimetype": mimetype,
            "response_format": "json_object",
        },
    )
    res = res.get("output")
    print("🟢 run_extraction: respuesta extracción recibida")

    if isinstance(res, str):
        res = json.loads(res)

    # 4) Ajustar invoice_type de salida
    if isinstance(res.get("invoice_type"), dict):
        existing_conf = float(res["invoice_type"].get("confidence") or 0.0)
        res["invoice_type"]["verbatim"] = itype
        res["invoice_type"]["confidence"] = max(existing_conf, conf)
    else:
        res["invoice_type"] = {"verbatim": itype, "confidence": conf}

    print("🟢 run_extraction: fin OK")
    return res


def run_unified_extraction_v2(
    *, data: bytes, filename: str, mimetype: str, engine: str = "mini", context=None,
):
    """Classify and transcribe one invoice in a single schema-bound call."""
    schema = json.loads(_load(SCHEMA_UNIFIED_V2))
    layout = _layout_or_empty(data, mimetype)
    ocr_text = transcript(layout)
    semantic_prompt = _load(PROMPT_UNIFIED_V2)
    if ocr_text:
        semantic_prompt += "\n\nIndependent OCR/layout transcript (line IDs are authoritative OCR identifiers; verify semantics against the image):\n" + ocr_text
    response = run_llm(
        context=context,
        app_id="wp_invoices",
        prompt=semantic_prompt,
        model=MODEL_MAP.get(engine, MODEL_MAP["mini"]),
        options={
            "file_bytes": data,
            "filename": filename,
            "mimetype": mimetype,
            "response_format": "json_schema",
            "schema_name": "invoice_extraction_v2",
            "json_schema": schema,
        },
    )
    extracted = response.get("output")
    if not isinstance(extracted, dict) or "_raw" in extracted:
        raise ValueError("structured_extraction_invalid")
    return {
        "extracted": extracted,
        "usage": response.get("usage") or {},
        "raw_output": response.get("raw_output"),
        "provider_request_id": response.get("provider_request_id"),
        "provider_response_id": response.get("provider_response_id"),
        "model": MODEL_MAP.get(engine, MODEL_MAP["mini"]),
        "ocr_layout": layout,
    }


def run_item_recovery_v2(
    *, data: bytes, filename: str, mimetype: str, engine: str = "mini", context=None,
    supplier_hints: str = "", visual_hints: str = "",
):
    """Re-read only line items after deterministic coverage detects omission."""
    schema = json.loads(_load(SCHEMA_ITEMS_RECOVERY_V2))
    response = run_llm(
        context=context, app_id="wp_invoices",
        prompt=_load(PROMPT_ITEMS_RECOVERY_V2)
        + ("\n\n" + supplier_hints if supplier_hints else "")
        + ("\n\nUnconsumed independent OCR item-line candidates:\n" + visual_hints if visual_hints else ""),
        model=MODEL_MAP.get(engine, MODEL_MAP["mini"]),
        options={
            "file_bytes": data, "filename": filename, "mimetype": mimetype,
            "response_format": "json_schema", "schema_name": "invoice_items_recovery_v2",
            "json_schema": schema,
        },
    )
    recovered = response.get("output")
    if not isinstance(recovered, dict) or not isinstance(recovered.get("patches"), list):
        raise ValueError("structured_item_recovery_invalid")
    return {
        "patches": recovered["patches"], "parsed": recovered,
        "usage": response.get("usage") or {},
        "raw_output": response.get("raw_output"),
        "provider_request_id": response.get("provider_request_id"),
        "provider_response_id": response.get("provider_response_id"),
        "model": MODEL_MAP.get(engine, MODEL_MAP["mini"]),
    }


def _layout_or_empty(data: bytes, mimetype: str) -> dict:
    try:
        return extract_layout(data, mimetype)
    except Exception as exc:
        return {"engine": "tesseract", "status": "failed", "error_type": type(exc).__name__,
                "lines": [], "item_line_candidates": []}
