"""Production Starlette ASGI REST API for Orxaq credit risk."""

from __future__ import annotations

import datetime
import json
import tempfile

from starlette.applications import Starlette
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.routing import Route

from orxaq.experiences.openapi import generate_spec
from orxaq.experiences.schemas import (
    ECLComputeRequest,
    FilingGenerateRequest,
    RegulatoryExportRequest,
    ScenarioCompareRequest,
    StressTestRequest,
)

_NO_AUTH = {"/api/v1/health", "/openapi.json", "/docs"}


class _AuthGate(BaseHTTPMiddleware):
    def __init__(self, app, auth_middleware=None) -> None:
        super().__init__(app)
        self._auth = auth_middleware

    async def dispatch(self, request: Request, call_next):
        if self._auth is None or request.url.path in _NO_AUTH:
            return await call_next(request)
        headers = dict(request.headers)
        result = self._auth.authenticate(headers, request.url.path)
        if not result.authenticated:
            return JSONResponse({"error": result.error or "unauthorized"}, status_code=401)
        return await call_next(request)


async def _parse_body(request: Request) -> dict[str, object]:
    try:
        result: dict[str, object] = await request.json()
        return result
    except Exception:
        return {}


async def _health(request: Request) -> JSONResponse:
    try:
        import orxaq

        v = getattr(orxaq, "__version__", "0.0.0")
    except Exception:
        v = "0.0.0"
    return JSONResponse(
        {"status": "ok", "version": v, "timestamp": datetime.datetime.now(datetime.UTC).isoformat()}
    )


async def _ontology(request: Request) -> JSONResponse:
    from orxaq.credit.ontology import build_credit_ontology

    dag = build_credit_ontology()
    return JSONResponse(
        {"entities": list(getattr(dag, "nodes", [])), "edges": list(getattr(dag, "edges", []))}
    )


async def _ecl_compute(request: Request) -> JSONResponse:
    body = await _parse_body(request)
    req = ECLComputeRequest.from_dict(body)
    if req.portfolio_size <= 0:
        return JSONResponse({"error": "portfolio_size must be > 0"}, status_code=400)
    from orxaq.credit.causal_pd import CausalPDModel
    from orxaq.credit.cecl import CECLEngine
    from orxaq.credit.ontology import build_credit_ontology
    from orxaq.credit.synthetic import CreditDataGenerator

    dag = build_credit_ontology()
    data = CreditDataGenerator(dag, seed=req.seed).generate(req.portfolio_size)
    model = CausalPDModel(dag)
    model.fit(data)
    engine = CECLEngine(model, dag)
    h = req.horizon
    result = (
        engine.expected_credit_loss(data)
        if isinstance(h, str) and h == "dynamic"
        else engine.expected_credit_loss(data, horizon_quarters=int(h))
    )
    segs: dict = {}
    for rec in data:
        seg = str(rec.get("sector", "unknown"))
        segs.setdefault(seg, {"count": 0})["count"] += 1
    return JSONResponse(
        {
            "ecl": result.total_ecl,
            "confidence_band": list(result.confidence_band),
            "segments": segs,
            "pd_mean": result.pd_mean,
            "horizon_quarters": result.horizon_quarters,
            "horizon_method": result.horizon_method,
        }
    )


async def _filing_generate(request: Request) -> JSONResponse:
    body = await _parse_body(request)
    req = FilingGenerateRequest.from_dict(body)
    from orxaq.credit.filing import FilingConfig, FilingGenerator

    out_dir = req.output_dir or tempfile.mkdtemp(prefix="orxaq_filing_")
    cfg = FilingConfig(
        institution_id=req.institution_id,
        period=req.period,
        output_dir=out_dir,
        seed=req.seed,
        n_samples=req.n_samples,
        horizon_quarters=req.horizon_quarters,
    )
    result = FilingGenerator(cfg).generate()
    return JSONResponse(
        {
            "output_dir": result.output_dir,
            "files": result.files,
            "audit_entries": result.audit_entries_count,
            "duration_ms": round(result.duration_seconds * 1000, 2),
            "period": result.period,
            "institution_id": result.institution_id,
        }
    )


async def _stress_test_run(request: Request) -> JSONResponse:
    body = await _parse_body(request)
    req = StressTestRequest.from_dict(body)
    from orxaq.credit.causal_pd import CausalPDModel
    from orxaq.credit.cecl import CECLEngine
    from orxaq.credit.ontology import build_credit_ontology
    from orxaq.credit.portfolio import LoanExposure
    from orxaq.credit.stress_report import FED_SCENARIOS, run_stress_test
    from orxaq.credit.synthetic import CreditDataGenerator

    if req.scenario not in FED_SCENARIOS:
        return JSONResponse({"error": f"Unknown scenario: {req.scenario}"}, status_code=400)
    dag = build_credit_ontology()
    data = CreditDataGenerator(dag, seed=req.seed).generate(req.portfolio_size)
    pm = CausalPDModel(dag)
    pm.fit(data)
    eng = CECLEngine(pm, dag)
    exps = [
        LoanExposure(
            loan_id=str(i),
            pd=max(0.001, min(0.999, float(r.get("default_flag", 0.05)))),
            lgd=0.45,
            ead=float(r.get("loan_amount", 10_000.0)),
            sector=str(r.get("sector", "retail")),
            vintage="2024",
        )
        for i, r in enumerate(data[:50])
    ]
    rpt = run_stress_test(
        exps, FED_SCENARIOS, pm, eng, horizon_quarters=req.horizon, scenario_name=req.scenario
    )
    ecl = rpt.quarterly_ecl
    peak = f"Q{ecl.index(max(ecl)) + 1}" if ecl else "Q1"
    return JSONResponse(
        {
            "scenario": rpt.scenario_name,
            "quarterly_ecl": ecl,
            "peak_quarter": peak,
            "portfolio_loss_rate": rpt.portfolio_loss_rate,
            "capital_impact": rpt.capital_impact,
        }
    )


async def _scenarios_compare(request: Request) -> JSONResponse:
    body = await _parse_body(request)
    req = ScenarioCompareRequest.from_dict(body)
    from orxaq.credit.causal_pd import CausalPDModel
    from orxaq.credit.cecl import CECLEngine
    from orxaq.credit.ontology import build_credit_ontology
    from orxaq.credit.portfolio import LoanExposure
    from orxaq.credit.scenario_comparison import ScenarioComparator
    from orxaq.credit.stress_report import FED_SCENARIOS, run_stress_test
    from orxaq.credit.synthetic import CreditDataGenerator

    unknown = [s for s in req.scenarios if s not in FED_SCENARIOS]
    if unknown:
        return JSONResponse({"error": f"Unknown scenarios: {unknown}"}, status_code=400)
    dag = build_credit_ontology()
    data = CreditDataGenerator(dag, seed=req.seed).generate(req.portfolio_size)
    pm = CausalPDModel(dag)
    pm.fit(data)
    eng = CECLEngine(pm, dag)
    exps = [
        LoanExposure(
            loan_id=str(i),
            pd=max(0.001, min(0.999, float(r.get("default_flag", 0.05)))),
            lgd=0.45,
            ead=float(r.get("loan_amount", 10_000.0)),
            sector=str(r.get("sector", "retail")),
            vintage="2024",
        )
        for i, r in enumerate(data[:50])
    ]
    reports = {
        n: run_stress_test(exps, FED_SCENARIOS, pm, eng, scenario_name=n) for n in req.scenarios
    }
    result = ScenarioComparator(reports).compare(starting_cet1=req.cet1_ratio)
    return JSONResponse(result.to_dict())


async def _regulatory_export(request: Request) -> JSONResponse:
    body = await _parse_body(request)
    req = RegulatoryExportRequest.from_dict(body)
    from orxaq.credit.regulatory_export import RegulatoryExporter

    exp = RegulatoryExporter(institution_id=req.institution_id, reporting_period=req.period)
    raw = (
        exp.export_ccar(format=req.format)
        if req.framework == "ccar"
        else exp.export_cecl(format=req.format)
    )
    try:
        content = json.loads(raw) if isinstance(raw, str) else raw
    except Exception:
        content = raw
    return JSONResponse(
        {
            "framework": req.framework,
            "content": content,
            "institution_id": req.institution_id,
            "period": req.period,
        }
    )


async def _openapi_json(request: Request) -> JSONResponse:
    return JSONResponse(generate_spec())


async def _openapi_docs(request: Request) -> Response:
    html = f"<html><body><pre>{json.dumps(generate_spec(), indent=2)}</pre></body></html>"
    return Response(html, media_type="text/html")


def make_app(auth_middleware=None) -> Starlette:
    """Create and return the Starlette ASGI application."""
    routes = [
        Route("/api/v1/health", _health, methods=["GET"]),
        Route("/api/v1/ontology", _ontology, methods=["GET"]),
        Route("/api/v1/ecl/compute", _ecl_compute, methods=["POST"]),
        Route("/api/v1/filing/generate", _filing_generate, methods=["POST"]),
        Route("/api/v1/stress-test/run", _stress_test_run, methods=["POST"]),
        Route("/api/v1/scenarios/compare", _scenarios_compare, methods=["POST"]),
        Route("/api/v1/regulatory/export", _regulatory_export, methods=["POST"]),
        Route("/openapi.json", _openapi_json, methods=["GET"]),
        Route("/docs", _openapi_docs, methods=["GET"]),
    ]
    app = Starlette(routes=routes)
    if auth_middleware is not None:
        app = _AuthGate(app, auth_middleware)  # type: ignore[assignment]
    return app  # type: ignore[return-value]


__all__ = ["make_app"]
