import base64
import json
from pathlib import Path
from typing import Any

from bridge_platform.apps.contracts import ActionContract, AppManifest, WordPressPackageContract
from bridge_platform.apps.envelopes import error_envelope, success_envelope
from bridge_platform.interapp.client import call_app
from bridge_platform.logging.platform_logger import get_platform_logger, log_structured
from bridge_platform.storage.service import StorageQuotaExceeded, write_bytes
from bridge_platform.quotas.service import QuotaExceeded


LOGGER = get_platform_logger("wp_invoices")


APP_MANIFEST = AppManifest(
    app_id="wp_invoices",
    display_name="Invoice Processor",
    app_version="1.1.6",
    description="Extract and validate normalized data from invoice documents.",
    actions={
        "health_v1": ActionContract(
            capability="platform.health",
            description="Report local invoice processor readiness without using AI.",
            output_schema={
                "type": "object",
                "additionalProperties": False,
                "required": ["healthy", "ai_checked", "app_version", "extractor_version"],
                "properties": {
                    "healthy": {"type": "boolean"},
                    "ai_checked": {"type": "boolean"},
                    "app_version": {"type": "string"},
                    "extractor_version": {"type": "string"},
                },
            },
            errors=(),
        ),
        "submit_v1": ActionContract(
            capability="invoice.document.submit",
            description="Submit one PDF or image invoice for synchronous extraction.",
            input_schema={
                "type": "object",
                "additionalProperties": False,
                "required": ["filename", "content_type", "file_bytes_base64", "idempotency_key"],
                "properties": {
                    "filename": {"type": "string", "minLength": 1, "maxLength": 255},
                    "content_type": {
                        "enum": ["application/pdf", "image/jpeg", "image/png", "image/webp"],
                    },
                    "file_bytes_base64": {"type": "string", "minLength": 1},
                    "idempotency_key": {"type": "string", "minLength": 8, "maxLength": 128},
                    "engine": {"enum": ["mini", "thinking"]},
                    "force_uncached": {"type": "boolean"},
                },
            },
            output_schema={
                "type": "object",
                "required": ["invoice_id", "status", "result", "artifacts"],
            },
            side_effects=True,
            idempotent=False,
            idempotency_key_required=True,
            timeout_seconds=300,
            data_classification="restricted",
            errors=(
                "invalid_input",
                "unsupported_document_type",
                "document_too_large",
                "storage_quota_exceeded",
                "ai_quota_exceeded",
                "extraction_failed",
            ),
        ),
        "submit_trusted_v1": ActionContract(
            capability="invoice.document.submit_trusted",
            description="Submit one invoice through unified extraction and deterministic quality review.",
            input_schema={
                "type": "object", "additionalProperties": False,
                "required": ["filename", "content_type", "file_bytes_base64", "idempotency_key"],
                "properties": {
                    "filename": {"type": "string", "minLength": 1, "maxLength": 255},
                    "content_type": {"enum": ["application/pdf", "image/jpeg", "image/png", "image/webp"]},
                    "file_bytes_base64": {"type": "string", "minLength": 1},
                    "idempotency_key": {"type": "string", "minLength": 8, "maxLength": 128},
                    "engine": {"enum": ["mini", "thinking"]},
                    "force_uncached": {"type": "boolean"},
                },
            },
            output_schema={"type": "object", "required": ["invoice_id", "status", "result", "artifacts", "supplier_profile"]},
            side_effects=True, idempotent=False, idempotency_key_required=True,
            timeout_seconds=300, data_classification="restricted",
            errors=("invalid_input", "unsupported_document_type", "document_too_large", "storage_quota_exceeded", "ai_quota_exceeded", "extraction_failed"),
        ),
        "result_v1": ActionContract(
            capability="invoice.result.read",
            description="Read one tenant-isolated canonical invoice result by opaque invoice ID.",
            input_schema={"type": "object", "additionalProperties": False, "required": ["invoice_id"],
                          "properties": {"invoice_id": {"type": "string", "pattern": "^inv_[a-f0-9]{32}$"}}},
            output_schema={"type": "object", "required": ["invoice_id", "status", "result"]},
            data_classification="restricted", errors=("invalid_input", "invoice_not_found"),
        ),
        "smoke_diagnostics_v1": ActionContract(
            capability="invoice.diagnostics.run",
            description="Run restricted OCR and recovery-schema smoke tests without processing the stored invoice through AI.",
            input_schema={"type": "object", "additionalProperties": False, "required": ["invoice_id", "idempotency_key"],
                          "properties": {"invoice_id": {"type": "string", "pattern": "^inv_[a-f0-9]{32}$"},
                                         "idempotency_key": {"type": "string", "minLength": 8, "maxLength": 128},
                                         "test": {"enum": ["all", "ocr_layout", "recovery_schema", "forced_full", "audit_report"]},
                                         "ocr_smoke_request_id": {"type": "string", "pattern": "^[a-f0-9-]{36}$"},
                                         "recovery_smoke_request_id": {"type": "string", "pattern": "^[a-f0-9-]{36}$"},
                                         "read_request_id": {"type": "string", "pattern": "^[a-f0-9-]{36}$"}}},
            output_schema={"type": "object", "required": ["pipeline_version", "ocr_passed", "recovery_schema_passed", "artifact_root"]},
            side_effects=True, idempotent=False, idempotency_key_required=True, data_classification="restricted",
            timeout_seconds=180,
            errors=("invalid_input", "extraction_failed"),
        ),
        "confirm_supplier_profile_v1": ActionContract(
            capability="invoice.supplier_profile.confirm",
            description="Activate allowlisted supplier-layout corrections after explicit user confirmation.",
            input_schema=json.loads((Path(__file__).parent / "contracts/supplier-profile-confirm.v1.schema.json").read_text()),
            output_schema={"type": "object", "required": ["invoice_id", "supplier_profile"]},
            side_effects=True, idempotent=True, data_classification="restricted",
            errors=("invalid_input", "confirmation_identity_required", "invoice_not_found", "supplier_profile_not_found"),
        ),
    },
    wordpress_package=WordPressPackageContract(
        package_id="wp_invoices_wp",
        version="1.1.6-dev",
        source_directory="wordpress-pack/wp-invoices-wp",
        entrypoint="wp-invoices-wp.php",
        plugin_file="wp-invoices-wp/wp-invoices-wp.php",
        capabilities=("invoice.document.submit_trusted", "invoice.result.read", "invoice.supplier_profile.confirm", "invoice.diagnostics.run"),
    ),
)


def register_blueprints(app):
    from .api.v1.blueprint import bp as bp_v1

    # /invoices/v1/...
    app.register_blueprint(bp_v1)


def handle_request(context, action):
    if action == "health_v1":
        return _health(context)
    if action == "submit_v1":
        return _submit(context)
    if action == "submit_trusted_v1":
        return _submit(context, trusted_v2=True)
    if action == "result_v1":
        return _result(context)
    if action == "smoke_diagnostics_v1":
        return _smoke_diagnostics(context)
    if action == "confirm_supplier_profile_v1":
        return _confirm_supplier_profile(context)
    if action == "test":
        return {
            "status": "ok",
            "app": "wp_invoices",
            "tenant": context["tenant"]["tenant_id"],
        }
    if action == "process":
        return _handle_process(context)
    if action.endswith("_v1"):
        return error_envelope(
            app_id=APP_MANIFEST.app_id,
            action=action,
            request_id=_request_id(context),
            code="action_not_found",
            message="The requested action is not supported.",
        ), 404
    return {"status": "error", "message": f"Unknown action: {action}"}, 404


def _health(context):
    log_structured(
        LOGGER,
        "wp_invoices_health_checked",
        request_id=_request_id(context),
        tenant_id=str((((context or {}).get("tenant") or {}).get("tenant_id")) or ""),
        outcome="healthy",
    )
    return success_envelope(
        app_id=APP_MANIFEST.app_id,
        action="health_v1",
        request_id=_request_id(context),
        data={
            "healthy": True,
            "ai_checked": False,
            "app_version": APP_MANIFEST.app_version,
            "extractor_version": "unified-v2.3-ocr-layout",
        },
    )


def _request_id(context) -> str:
    return str((context or {}).get("request_id") or "")


def _smoke_diagnostics(context):
    from apps.wp_invoices.services.smoke_diagnostics import run_smoke_tests
    try:
        data = run_smoke_tests(context)
    except ValueError:
        return error_envelope(app_id=APP_MANIFEST.app_id, action="smoke_diagnostics_v1",
                              request_id=_request_id(context), code="invalid_input",
                              message="The smoke-test request is invalid."), 400
    except Exception:
        return error_envelope(app_id=APP_MANIFEST.app_id, action="smoke_diagnostics_v1",
                              request_id=_request_id(context), code="extraction_failed",
                              message="The smoke tests could not be completed."), 502
    return success_envelope(app_id=APP_MANIFEST.app_id, action="smoke_diagnostics_v1",
                            request_id=_request_id(context), data=data)


def _submit(context, trusted_v2=False):
    from apps.wp_invoices.services.submission import submit_invoice, submit_invoice_v2

    try:
        data = (submit_invoice_v2 if trusted_v2 else submit_invoice)(context)
    except ValueError as exc:
        code = str(exc) if str(exc) in {
            "invalid_input", "unsupported_document_type", "document_too_large",
        } else "invalid_input"
        messages = {
            "invalid_input": "The invoice submission is invalid.",
            "unsupported_document_type": "Only PDF, JPEG, PNG, and WebP invoices are supported.",
            "document_too_large": "The invoice document exceeds the maximum allowed size.",
        }
        return error_envelope(
            app_id=APP_MANIFEST.app_id,
            action="submit_trusted_v1" if trusted_v2 else "submit_v1",
            request_id=_request_id(context),
            code=code,
            message=messages[code],
        ), 413 if code == "document_too_large" else 400
    except StorageQuotaExceeded as exc:
        return error_envelope(
            app_id=APP_MANIFEST.app_id,
            action="submit_trusted_v1" if trusted_v2 else "submit_v1",
            request_id=_request_id(context),
            code="storage_quota_exceeded",
            message="The tenant storage quota does not allow this invoice.",
            details={"requested_bytes": exc.requested_bytes},
        ), 413
    except QuotaExceeded:
        return error_envelope(
            app_id=APP_MANIFEST.app_id,
            action="submit_trusted_v1" if trusted_v2 else "submit_v1",
            request_id=_request_id(context),
            code="ai_quota_exceeded",
            message="The tenant AI allowance is not sufficient to process this invoice.",
            retryable=False,
        ), 429
    except Exception:
        log_structured(
            LOGGER,
            "wp_invoices_submission_failed",
            request_id=_request_id(context),
            tenant_id=str((((context or {}).get("tenant") or {}).get("tenant_id")) or ""),
            outcome="failed",
            error_code="extraction_failed",
        )
        return error_envelope(
            app_id=APP_MANIFEST.app_id,
            action="submit_trusted_v1" if trusted_v2 else "submit_v1",
            request_id=_request_id(context),
            code="extraction_failed",
            message="The invoice could not be processed.",
            retryable=True,
        ), 502
    log_structured(
        LOGGER,
        "wp_invoices_submission_completed",
        request_id=_request_id(context),
        tenant_id=str((((context or {}).get("tenant") or {}).get("tenant_id")) or ""),
        invoice_id=data["invoice_id"],
        outcome="completed",
    )
    return success_envelope(
        app_id=APP_MANIFEST.app_id,
        action="submit_trusted_v1" if trusted_v2 else "submit_v1",
        request_id=_request_id(context),
        data=data,
        meta={"processing_mode": "synchronous"},
    )


def _result(context):
    from apps.wp_invoices.services.submission import read_invoice_result
    try:
        data = read_invoice_result(context)
    except ValueError:
        return _contract_error(context, "result_v1", "invalid_input", "The invoice ID is invalid.", 400)
    except LookupError:
        return _contract_error(context, "result_v1", "invoice_not_found", "The invoice result was not found.", 404)
    return success_envelope(app_id=APP_MANIFEST.app_id, action="result_v1", request_id=_request_id(context), data=data)


def _confirm_supplier_profile(context):
    from apps.wp_invoices.services.submission import confirm_invoice_supplier_profile
    try:
        data = confirm_invoice_supplier_profile(context)
    except PermissionError:
        return _contract_error(context, "confirm_supplier_profile_v1", "confirmation_identity_required", "A signed-in user must confirm this profile.", 403)
    except ValueError:
        return _contract_error(context, "confirm_supplier_profile_v1", "invalid_input", "The profile correction is invalid.", 400)
    except LookupError as exc:
        code = "invoice_not_found" if str(exc) == "invoice_not_found" else "supplier_profile_not_found"
        return _contract_error(context, "confirm_supplier_profile_v1", code, "The supplier profile could not be confirmed.", 404)
    return success_envelope(app_id=APP_MANIFEST.app_id, action="confirm_supplier_profile_v1", request_id=_request_id(context), data=data)


def _contract_error(context, action, code, message, status):
    return error_envelope(app_id=APP_MANIFEST.app_id, action=action, request_id=_request_id(context), code=code, message=message), status


def _handle_process(context):
    from apps.wp_invoices.services.analyzer import analyze
    from apps.wp_invoices.services.invoice_revision_pdf import create_invoice_revision_pdf
    from apps.wp_invoices.services.pipeline import process_invoice_bytes

    payload = context.get("payload") or {}
    filename = str(payload.get("filename") or "").strip()
    file_bytes_base64 = str(payload.get("file_bytes_base64") or "").strip()
    if not filename:
        return {"status": "error", "message": "Missing filename"}, 400
    if not file_bytes_base64:
        return {"status": "error", "message": "Missing file_bytes_base64"}, 400

    try:
        file_bytes = _decode_file_bytes(file_bytes_base64)
    except Exception as exc:
        return {"status": "error", "message": f"Invalid file_bytes_base64: {exc}"}, 400

    content_type = str(payload.get("content_type") or "application/octet-stream")
    engine = payload.get("engine")
    options = payload.get("options") or {}

    app_storage_root = _resolve_app_storage(context)
    staging_dir = app_storage_root / "staging" / str(context.get("request_id") or "request")
    revisions_dir = app_storage_root / "revisions"
    staging_dir.mkdir(parents=True, exist_ok=True)
    revisions_dir.mkdir(parents=True, exist_ok=True)

    try:
        original_path = write_bytes(
            tenant_id=context["tenant"]["tenant_id"],
            app_id="wp_invoices",
            relative_path=Path("staging") / str(context.get("request_id") or "request") / filename,
            data=file_bytes,
        )
    except StorageQuotaExceeded as exc:
        return {
            "status": "error",
            "code": "storage_quota_exceeded",
            "message": str(exc),
            "storage": exc.snapshot.to_dict(),
        }, 413

    pipeline_result = process_invoice_bytes(
        file_bytes=file_bytes,
        filename=filename,
        content_type=content_type,
        engine=engine,
        context=context,
    )
    extracted = pipeline_result.get("extracted") or {}
    checks = dict(pipeline_result.get("checks") or {})
    analysis = analyze(extracted)

    tax_lookup_result = None
    if options.get("tax_lookup") is True:
        abn = _extract_abn_for_lookup(extracted)
        if abn:
            tax_lookup_result = call_app(
                context=context,
                app="abn_lookup_app",
                action="lookup",
                payload={"abn": abn},
            )
        else:
            tax_lookup_result = {"status": "error", "app": "abn_lookup_app", "message": "ABN not found"}

        if isinstance(tax_lookup_result, dict):
            checks["tax_lookup"] = tax_lookup_result

    extracted_path = staging_dir / "extracted.json"
    analysis_path = staging_dir / "analysis.json"
    extracted_path.write_text(json.dumps(extracted, ensure_ascii=False, indent=2), encoding="utf-8")
    analysis_path.write_text(json.dumps(analysis, ensure_ascii=False, indent=2), encoding="utf-8")
    if tax_lookup_result is not None:
        (staging_dir / "tax_lookup.json").write_text(
            json.dumps(tax_lookup_result, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )

    pdf_path = create_invoice_revision_pdf(
        extracted=extracted,
        output_dir=str(revisions_dir),
        original_filename=filename,
        checks=checks,
    )

    return {
        "status": "ok",
        "tenant": context["tenant"]["tenant_id"],
        "app": "wp_invoices",
        "action": "process",
        "result": {
            "extracted": extracted,
            "analysis": analysis,
            "tax_lookup": tax_lookup_result,
            "pdf": str(pdf_path),
        },
    }


def _decode_file_bytes(encoded: str) -> bytes:
    raw = encoded.strip()
    if "," in raw and raw.split(",", 1)[0].startswith("data:"):
        raw = raw.split(",", 1)[1]
    return base64.b64decode(raw, validate=True)


def _resolve_app_storage(context) -> Path:
    base_dir = Path(__file__).resolve().parents[2]
    storage_root = Path(str(context.get("storage") or "storage/tenants/unknown"))
    if not storage_root.is_absolute():
        storage_root = base_dir / storage_root
    return storage_root / "wp_invoices"


def _extract_abn_for_lookup(extracted: dict[str, Any]) -> str | None:
    header = extracted.get("header") or {}
    supplier = header.get("supplier") or {}
    for key in ("abn", "tax_id"):
        field = supplier.get(key) or {}
        if not isinstance(field, dict):
            continue
        value = field.get("computed") or field.get("verbatim")
        if value:
            normalized = str(value).replace(" ", "").replace("ABN#", "").strip()
            if normalized:
                return normalized
    return None
