"""Tenant-aware synchronous submission service for invoice contract v1."""

from __future__ import annotations

import base64
import binascii
import hashlib
import json
import re
from pathlib import Path
from typing import Any

from bridge_platform.storage.service import read_bytes, write_bytes

from apps.wp_invoices.services.pipeline import extract_canonical_invoice, extract_canonical_invoice_v2
from apps.wp_invoices.services.supplier_profiles import (
    confirm_profile, load_profile, stage_candidate, supplier_key,
)


APP_ID = "wp_invoices"
MAX_DOCUMENT_BYTES = 15 * 1024 * 1024
SUPPORTED_CONTENT_TYPES = {
    "application/pdf": ".pdf",
    "image/jpeg": ".jpg",
    "image/png": ".png",
    "image/webp": ".webp",
}


def submit_invoice(context: dict[str, Any]) -> dict[str, Any]:
    return _submit_invoice(context, trusted_v2=False)


def submit_invoice_v2(context: dict[str, Any]) -> dict[str, Any]:
    """Submit using the unified extractor and deterministic trust gate."""
    return _submit_invoice(context, trusted_v2=True)


def _submit_invoice(context: dict[str, Any], *, trusted_v2: bool) -> dict[str, Any]:
    payload = context.get("payload") or {}
    tenant_id = str(((context.get("tenant") or {}).get("tenant_id")) or "").strip()
    filename = _safe_filename(payload.get("filename"))
    content_type = str(payload.get("content_type") or "").strip().lower()
    idempotency_key = str(payload.get("idempotency_key") or "").strip()
    engine = str(payload.get("engine") or "mini").strip()
    force_uncached = payload.get("force_uncached") is True
    if not tenant_id or not filename or not 8 <= len(idempotency_key) <= 128:
        raise ValueError("invalid_input")
    if content_type not in SUPPORTED_CONTENT_TYPES:
        raise ValueError("unsupported_document_type")
    if engine not in {"mini", "thinking"}:
        raise ValueError("invalid_input")

    document = _decode_document(payload.get("file_bytes_base64"))
    _validate_signature(document, content_type)
    invoice_id = _invoice_id(tenant_id, idempotency_key)
    artifact_id = f"invoice:{invoice_id}:original"
    extension = SUPPORTED_CONTENT_TYPES[content_type]
    cache_status = "not_applicable"
    if trusted_v2 and not force_uncached:
        cached = _cached_trusted_result(tenant_id=tenant_id, invoice_id=invoice_id)
        if cached is not None:
            try:
                profile = stage_candidate(tenant_id=tenant_id, result=cached)
            except Exception:
                profile = None
            return _submission_data(invoice_id, cached, profile)
        cache_status = "miss"
    elif trusted_v2:
        cache_status = "forced_miss"

    write_bytes(
        tenant_id=tenant_id,
        app_id=APP_ID,
        relative_path=Path("documents") / invoice_id / f"original{extension}",
        data=document,
    )
    extractor = extract_canonical_invoice_v2 if trusted_v2 else extract_canonical_invoice
    result = extractor(
        file_bytes=document,
        filename=filename,
        content_type=content_type,
        invoice_id=invoice_id,
        artifact_id=artifact_id,
        engine=engine,
        context=context,
    )
    pipeline_audit = result.pop("_pipeline_audit", None)
    if trusted_v2:
        result.setdefault("processing", {})["cache_status"] = cache_status
    result_artifact_id = f"invoice:{invoice_id}:result-json"
    write_bytes(
        tenant_id=tenant_id,
        app_id=APP_ID,
        relative_path=Path("results") / invoice_id / "result.json",
        data=json.dumps(result, ensure_ascii=False, separators=(",", ":")).encode("utf-8"),
    )
    profile = None
    if trusted_v2:
        try:
            profile = stage_candidate(tenant_id=tenant_id, result=result)
        except Exception:
            # Profile memory is auxiliary and must never invalidate an invoice result.
            profile = None
    submission_data = _submission_data(invoice_id, result, profile if trusted_v2 else None, include_profile=trusted_v2)
    if trusted_v2 and isinstance(pipeline_audit, dict):
        _persist_pipeline_audit(
            tenant_id=tenant_id, invoice_id=invoice_id,
            request_id=str(context.get("request_id") or "unknown"),
            manifest={
                "request_id": str(context.get("request_id") or ""),
                "invoice_id": invoice_id,
                "filename": filename,
                "content_type": content_type,
                "size_bytes": len(document),
                "document_sha256": hashlib.sha256(document).hexdigest(),
                "app_version": "1.1.6",
                "extractor_version": (result.get("processing") or {}).get("extractor_version"),
                "model": ((pipeline_audit.get("primary") or {}).get("model")),
                "cache_status": cache_status,
                "recovery_attempted": ((result.get("processing") or {}).get("recovery") or {}).get("attempted"),
                "recovery_accepted": ((result.get("processing") or {}).get("recovery") or {}).get("accepted"),
            },
            audit=pipeline_audit, wordpress_payload=submission_data,
        )
    return submission_data


def _persist_pipeline_audit(*, tenant_id: str, invoice_id: str, request_id: str,
                            manifest: dict[str, Any], audit: dict[str, Any],
                            wordpress_payload: dict[str, Any]) -> None:
    safe_request_id = re.sub(r"[^a-zA-Z0-9_-]", "_", request_id)[:128] or "unknown"
    root = Path("audits") / invoice_id / safe_request_id
    primary = audit.get("primary") or {}
    stages = {
        "01-request-manifest.json": _stage_value(manifest, "request_manifest"),
        "02-primary-raw.json": _stage_value(primary.get("raw"), "primary_raw"),
        "03-primary-parsed.json": _stage_value(primary.get("parsed"), "primary_parsed"),
        "04-primary-canonical.json": _stage_value(primary.get("canonical"), "primary_canonical"),
        "05-primary-reconciliation.json": _stage_value(primary.get("reconciliation"), "primary_reconciliation"),
        "06-recovery.json": _stage_value(audit.get("recovery"), "recovery", unavailable="not_completed"),
        "07-wordpress-response.json": _stage_value(wordpress_payload, "wordpress_response"),
        "08-ocr-layout.json": _stage_value(primary.get("ocr_layout"), "ocr_layout", unavailable="stage_failed_or_unavailable"),
        "09-visual-association.json": _stage_value(primary.get("visual_association"), "visual_association", unavailable="stage_failed_or_unavailable"),
    }
    for name, value in stages.items():
        write_bytes(
            tenant_id=tenant_id, app_id=APP_ID, relative_path=root / name,
            data=json.dumps(value, ensure_ascii=False, separators=(",", ":")).encode("utf-8"),
        )


def _stage_value(value: Any, stage: str, *, unavailable: str = "missing") -> dict[str, Any]:
    if value is None:
        return {"status": "error", "stage": stage, "error": {"code": unavailable}}
    if isinstance(value, dict) and value.get("status") == "failed":
        return {"status": "error", "stage": stage, "error": value}
    return {"status": "success", "stage": stage, "data": value}


def _submission_data(invoice_id, result, profile=None, *, include_profile=True):
    artifact_id = f"invoice:{invoice_id}:original"
    result_artifact_id = f"invoice:{invoice_id}:result-json"
    return {
        "invoice_id": invoice_id,
        "status": result["status"],
        "result": result,
        "artifacts": {
            "original": artifact_id,
            "result_json": result_artifact_id,
        },
        **({"supplier_profile": _public_profile(profile)} if include_profile else {}),
    }


def _cached_trusted_result(*, tenant_id: str, invoice_id: str) -> dict[str, Any] | None:
    try:
        result = json.loads(read_bytes(
            tenant_id=tenant_id, app_id=APP_ID,
            relative_path=Path("results") / invoice_id / "result.json",
        ))
    except (FileNotFoundError, json.JSONDecodeError):
        return None
    processing = result.get("processing") or {}
    return result if (
        processing.get("extractor_version") == "unified-v2.3-ocr-layout"
        and processing.get("canonical_revision") == "monetary-column-v1"
    ) else None


def read_invoice_result(context: dict[str, Any]) -> dict[str, Any]:
    tenant_id = _tenant_id(context)
    invoice_id = _validated_invoice_id((context.get("payload") or {}).get("invoice_id"))
    try:
        result = json.loads(read_bytes(
            tenant_id=tenant_id, app_id=APP_ID,
            relative_path=Path("results") / invoice_id / "result.json",
        ))
    except (FileNotFoundError, json.JSONDecodeError):
        raise LookupError("invoice_not_found") from None
    return {"invoice_id": invoice_id, "status": result.get("status", "completed"), "result": result}


def confirm_invoice_supplier_profile(context: dict[str, Any]) -> dict[str, Any]:
    tenant_id = _tenant_id(context)
    user_id = str(context.get("user_id") or "").strip()
    if not user_id:
        raise PermissionError("confirmation_identity_required")
    payload = context.get("payload") or {}
    invoice_id = _validated_invoice_id(payload.get("invoice_id"))
    stored = read_invoice_result({**context, "payload": {"invoice_id": invoice_id}})["result"]
    supplier = stored.get("supplier") or {}
    key = supplier_key(tax_id=supplier.get("tax_id"), name=supplier.get("name"))
    candidate = load_profile(tenant_id=tenant_id, key=key) if key else None
    if not candidate or candidate.get("audit", {}).get("created_from_invoice_id") != invoice_id:
        raise LookupError("supplier_profile_not_found")
    active = confirm_profile(
        tenant_id=tenant_id, candidate=candidate, user_id=user_id,
        corrections=payload.get("corrections") or {},
    )
    return {"invoice_id": invoice_id, "supplier_profile": _public_profile(active)}


def _public_profile(profile: dict[str, Any] | None) -> dict[str, Any] | None:
    if not profile:
        return None
    return {key: profile.get(key) for key in ("profile_id", "display_name", "status", "version", "invoice_type")}


def _tenant_id(context: dict[str, Any]) -> str:
    tenant_id = str(((context.get("tenant") or {}).get("tenant_id")) or "").strip()
    if not tenant_id:
        raise ValueError("invalid_input")
    return tenant_id


def _validated_invoice_id(value: Any) -> str:
    invoice_id = str(value or "").strip()
    if not re.fullmatch(r"inv_[a-f0-9]{32}", invoice_id):
        raise ValueError("invalid_input")
    return invoice_id


def _decode_document(encoded: Any) -> bytes:
    raw = str(encoded or "").strip()
    if "," in raw and raw.split(",", 1)[0].startswith("data:"):
        raw = raw.split(",", 1)[1]
    if not raw:
        raise ValueError("invalid_input")
    try:
        document = base64.b64decode(raw, validate=True)
    except (binascii.Error, ValueError):
        raise ValueError("invalid_input") from None
    if not document:
        raise ValueError("invalid_input")
    if len(document) > MAX_DOCUMENT_BYTES:
        raise ValueError("document_too_large")
    return document


def _validate_signature(document: bytes, content_type: str) -> None:
    valid = {
        "application/pdf": document.startswith(b"%PDF-"),
        "image/jpeg": document.startswith(b"\xff\xd8\xff"),
        "image/png": document.startswith(b"\x89PNG\r\n\x1a\n"),
        "image/webp": len(document) >= 12 and document[:4] == b"RIFF" and document[8:12] == b"WEBP",
    }
    if not valid.get(content_type, False):
        raise ValueError("unsupported_document_type")


def _invoice_id(tenant_id: str, idempotency_key: str) -> str:
    digest = hashlib.sha256(f"{tenant_id}\0{idempotency_key}".encode("utf-8")).hexdigest()
    return f"inv_{digest[:32]}"


def _safe_filename(value: Any) -> str:
    filename = Path(str(value or "")).name.replace("\x00", "").strip()
    return filename[:255]
