"""End-to-end regulatory filing generator for CECL/CCAR submissions.

Orchestrates: synthetic data → CECL → stress tests → regulatory
export (JSON/CSV) → HTML/Markdown reports → audit trail → SHA-256
manifest.  Stdlib-only per ADR-007.
"""

from __future__ import annotations

import csv
import hashlib
import json
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

# ── Constants ─────────────────────────────────────────────────

FILING_OUTPUT_FILES: list[str] = [
    "cecl_disclosure.json",
    "ccar_export.json",
    "schedule_h1.csv",
    "scenario_comparison.csv",
    "regulatory_report.html",
    "regulatory_report.md",
    "audit_trail.json",
    "manifest.json",
]


# ── Config / Result ────────────────────────────────────────────


@dataclass
class FilingConfig:
    """Configuration for a regulatory filing run."""

    institution_id: str = "BANK001"
    period: str = "2026Q1"
    output_dir: str = "."
    seed: int = 42
    n_samples: int = 500
    horizon_quarters: int = 9
    institution_name: str = "Orxaq Financial, N.A."
    methodology: str = "PD/LGD"
    portfolio_data: list[dict] | None = None  # pre-loaded data (skips synthetic gen)


@dataclass
class FilingResult:
    """Output of a completed filing generation run."""

    output_dir: str
    period: str
    institution_id: str
    duration_seconds: float
    audit_entries_count: int
    files: list[str] = field(default_factory=list)


# ── Portfolio enrichment ───────────────────────────────────────


def _enrich_portfolio_data(records: list[dict]) -> list[dict]:
    """Enrich real portfolio records for CausalPDModel fitting.

    Real loan tapes lack ``default_flag``. Synthesize it from observable
    credit risk factors using an empirical logistic rule so the PD model
    can learn meaningful weights from real data.

    Rule (mirrors Basel credit criteria):
        default_score = 0.6 * (credit_score < 650)
                      + 0.25 * (ltv > 0.85)
                      + 0.15 * (dti > 0.45)
        default_flag  = 1 if default_score > 0.5 else 0
    """
    enriched: list[dict] = []
    for rec in records:
        row = dict(rec)
        if "default_flag" not in row:
            cs = float(row.get("credit_score", row.get("fico_score", 700.0)))
            ltv = float(row.get("ltv", 0.75))
            dti = float(row.get("dti", 0.35))
            score = 0.6 * (cs < 650) + 0.25 * (ltv > 0.85) + 0.15 * (dti > 0.45)
            row["default_flag"] = 1.0 if score > 0.5 else 0.0
        enriched.append(row)
    return enriched


# ── Generator ─────────────────────────────────────────────────


class FilingGenerator:
    """End-to-end regulatory filing package generator.

    Usage::

        cfg = FilingConfig(institution_id="BANK001", period="2026Q1")
        result = FilingGenerator(cfg).generate()
        # or with late-bound config:
        result = FilingGenerator().generate(cfg)
    """

    def __init__(self, config: FilingConfig | None = None) -> None:
        self._default_config = config or FilingConfig()

    def generate(self, config: FilingConfig | None = None) -> FilingResult:
        """Run the full filing pipeline and write output files."""
        cfg = config or self._default_config
        out = Path(cfg.output_dir)
        out.mkdir(parents=True, exist_ok=True)

        start = time.monotonic()
        audit: list[dict[str, Any]] = []

        def _log(action: str, details: dict | None = None) -> None:
            audit.append(
                {
                    "action": action,
                    "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
                    **(details or {}),
                }
            )

        _log("filing_started", {"institution_id": cfg.institution_id})

        # ── 1. Build credit ontology DAG ──────────────────────
        from orxaq.credit.ontology import build_credit_ontology

        dag = build_credit_ontology()
        _log("ontology_built")

        # ── 2. Generate (or load) portfolio data ──────────────
        if cfg.portfolio_data is not None:
            data = cfg.portfolio_data
            _log("data_loaded", {"n_samples": len(data), "source": "file"})
        else:
            from orxaq.credit.synthetic import CreditDataGenerator

            gen = CreditDataGenerator(dag, seed=cfg.seed)
            data = gen.generate(cfg.n_samples)
            _log("data_generated", {"n_samples": len(data)})

        # ── 3. Fit PD model ───────────────────────────────────
        from orxaq.credit.causal_pd import CausalPDModel

        pd_model = CausalPDModel(dag)
        pd_model.fit(data)
        _log("pd_model_fitted")

        # ── 4. Compute CECL ECL ───────────────────────────────
        from orxaq.credit.cecl import CECLEngine

        engine = CECLEngine(pd_model, dag)
        ecl_result = engine.expected_credit_loss(data)
        _log("ecl_computed", {"total_ecl": round(ecl_result.total_ecl, 6)})

        # ── 5. Build portfolio exposures for stress test ─────
        from orxaq.credit.portfolio import LoanExposure

        exposures: list[LoanExposure] = []
        for rec in data[:200]:
            ead = abs(float(rec.get("loan_amount", rec.get("ead", 10_000.0))))
            pd_ = max(0.001, min(0.999, float(rec.get("default_flag", 0.05))))
            exposures.append(
                LoanExposure(
                    loan_id=str(rec.get("loan_id", id(rec))),
                    ead=ead,
                    pd=pd_,
                    lgd=0.45,
                    sector=str(rec.get("sector", rec.get("segment", "retail"))),
                    vintage=str(rec.get("vintage", "2024")),
                )
            )
        _log("exposures_built", {"n": len(exposures)})

        # ── 6. Run CCAR stress test (all three Fed scenarios) ────
        from orxaq.credit import run_stress_test
        from orxaq.credit.scenarios import ADVERSE, BASELINE, SEVERELY_ADVERSE

        scenarios = {
            "baseline": BASELINE,
            "adverse": ADVERSE,
            "severely_adverse": SEVERELY_ADVERSE,
        }
        stress_reports = {
            name: run_stress_test(
                exposures,
                scenarios,
                pd_model,
                engine,
                horizon_quarters=cfg.horizon_quarters,
                scenario_name=name,
            )
            for name in ("baseline", "adverse", "severely_adverse")
        }
        stress_report = stress_reports["severely_adverse"]
        _log("stress_test_completed")

        # ── 7. Build risk report ──────────────────────────────
        from orxaq.credit.reporting import RiskReportBuilder

        stressed_el = (
            getattr(
                stress_report.scenario_results.get("severely_adverse"),
                "total_loss",
                ecl_result.total_ecl * 1.5,
            )
            if hasattr(stress_report, "scenario_results")
            else ecl_result.total_ecl * 1.5
        )
        rpt = (
            RiskReportBuilder()
            .add_ecl_section(
                total_ecl=ecl_result.total_ecl,
                horizon_quarters=ecl_result.horizon_quarters,
                horizon_method=ecl_result.horizon_method,
                pd_mean=ecl_result.pd_mean,
                lgd=ecl_result.lgd,
                confidence_low=ecl_result.confidence_band[0],
                confidence_high=ecl_result.confidence_band[1],
            )
            .add_stress_section(
                scenario_name="severely_adverse",
                base_el=ecl_result.total_ecl,
                stressed_el=stressed_el,
                base_ec=ecl_result.total_ecl * 1.2,
                stressed_ec=stressed_el * 1.2,
            )
            .build()
        )
        _log("risk_report_built")

        # ── 8. Export CECL disclosure JSON ────────────────────
        from orxaq.credit.regulatory_export import RegulatoryExporter

        reg_exp = RegulatoryExporter(
            institution_id=cfg.institution_id,
            reporting_period=cfg.period,
            methodology=cfg.methodology,
        )
        cecl_data = reg_exp.export_cecl(format="json")
        if isinstance(cecl_data, str):
            cecl_payload = json.loads(cecl_data)
        else:
            cecl_payload = cecl_data
        cecl_payload.setdefault("framework", "ASC 326 (CECL)")
        cecl_payload.setdefault("institution_id", cfg.institution_id)
        cecl_payload.setdefault("period", cfg.period)
        _write_json(out / "cecl_disclosure.json", cecl_payload)
        _log("cecl_disclosure_written")

        # ── 9. Export CCAR JSON ────────────────────────────────
        ccar_data = reg_exp.export_ccar(format="json")
        if isinstance(ccar_data, str):
            ccar_payload = json.loads(ccar_data)
        else:
            ccar_payload = dict(ccar_data)
        ccar_payload.setdefault("framework", "CCAR/DFAST")
        ccar_payload.setdefault("institution_id", cfg.institution_id)
        _write_json(out / "ccar_export.json", ccar_payload)
        _log("ccar_export_written")

        # ── 10. Export Schedule H.1 CSV ───────────────────────
        ccar_schedule = reg_exp.build_fr_y14_schedule(
            stress_report=stress_report,
            ecl_result=ecl_result,
            institution_name=cfg.institution_name,
        )
        schedule_h1 = [
            {
                "quarter": q.quarter,
                "projected_loss": q.projected_loss,
                "ppnr_proxy": q.ppnr_proxy,
                "capital_ratio_impact": q.capital_ratio_impact,
            }
            for q in ccar_schedule.quarters
        ]
        _write_csv(out / "schedule_h1.csv", schedule_h1)
        _log("schedule_h1_written", {"rows": len(schedule_h1)})

        # ── 11. Export scenario comparison CSV ────────────────
        scenario_rows = _build_scenario_comparison_rich(stress_reports)
        _write_csv(out / "scenario_comparison.csv", scenario_rows)
        _log("scenario_comparison_written")

        # ── 12. Generate HTML and Markdown reports ────────────
        from orxaq.credit.regulatory_report import (
            RegulatorReportConfig,
            RegulatorReportExporter,
        )

        report_cfg = RegulatorReportConfig(
            institution_name=cfg.institution_name,
        )
        rpt_exp = RegulatorReportExporter(
            stress_report=stress_report,
            risk_report=rpt,
            ecl_result=ecl_result,
            portfolio=exposures,
            config=report_cfg,
        )
        html_content = rpt_exp.to_html()
        md_content = rpt_exp.to_markdown()
        (out / "regulatory_report.html").write_text(html_content, encoding="utf-8")
        (out / "regulatory_report.md").write_text(md_content, encoding="utf-8")
        _log("reports_written")

        # ── 13. Write audit trail ─────────────────────────────
        _log("filing_completed")
        _write_json(out / "audit_trail.json", audit)

        # ── 14. Write manifest with SHA-256 checksums ─────────
        files_in_manifest = [f for f in FILING_OUTPUT_FILES if f != "manifest.json"]
        manifest: dict[str, Any] = {
            "institution_id": cfg.institution_id,
            "period": cfg.period,
            "generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
            "files": {
                fname: {
                    "sha256": _sha256(out / fname),
                    "size_bytes": (out / fname).stat().st_size,
                }
                for fname in files_in_manifest
                if (out / fname).exists()
            },
        }
        # Add manifest itself after the fact (won't include its own hash)
        _write_json(out / "manifest.json", manifest)

        duration = time.monotonic() - start
        return FilingResult(
            output_dir=str(out),
            period=cfg.period,
            institution_id=cfg.institution_id,
            duration_seconds=duration,
            audit_entries_count=len(audit),
            files=FILING_OUTPUT_FILES[:],
        )


# ── Private helpers ────────────────────────────────────────────


def _write_json(path: Path, data: Any) -> None:
    path.write_text(json.dumps(data, indent=2, default=str), encoding="utf-8")


def _write_csv(path: Path, rows: list[dict]) -> None:
    if not rows:
        path.write_text("", encoding="utf-8")
        return
    with path.open("w", newline="", encoding="utf-8") as fh:
        writer = csv.DictWriter(fh, fieldnames=list(rows[0].keys()))
        writer.writeheader()
        writer.writerows(rows)


def _sha256(path: Path) -> str:
    h = hashlib.sha256()
    h.update(path.read_bytes())
    return h.hexdigest()


def _build_scenario_comparison(stress_report: Any) -> list[dict]:
    """Extract scenario comparison rows from StressTestReport (legacy fallback)."""
    rows: list[dict] = []
    try:
        for scenario_name, results in stress_report.scenario_results.items():
            for q_idx, q_result in enumerate(results.quarterly_results):
                rows.append(
                    {
                        "scenario": scenario_name,
                        "quarter": q_idx + 1,
                        "ecl": round(getattr(q_result, "ecl", 0.0), 6),
                        "pd_mean": round(getattr(q_result, "pd_mean", 0.0), 6),
                    }
                )
    except (AttributeError, TypeError):
        rows = [{"scenario": "severely_adverse", "quarter": 1, "ecl": 0.0}]
    return rows


def _build_scenario_comparison_rich(stress_reports: dict[str, Any]) -> list[dict]:
    """Build enriched scenario comparison CSV rows via ScenarioComparator."""
    from orxaq.credit.scenario_comparison import ScenarioComparator  # noqa: PLC0415

    try:
        comparator = ScenarioComparator(stress_reports)
        result = comparator.compare()
        rows: list[dict] = []
        wf = result.capital_waterfall
        attr = result.sensitivity_attribution
        top_driver = max(attr, key=attr.get) if attr else ""  # type: ignore[arg-type]
        for scenario in result.scenarios:
            deltas = result.ecl_deltas.get(scenario, [])
            peak_q = result.peak_loss_quarter.get(scenario, "")
            scenario_wf = wf.get(scenario, {})
            for i, label in enumerate(result.quarter_labels):
                rows.append(
                    {
                        "scenario": scenario,
                        "quarter": label,
                        "ecl_delta_vs_baseline": round(deltas[i] if i < len(deltas) else 0.0, 8),
                        "is_peak_quarter": label == peak_q,
                        "starting_cet1": scenario_wf.get("starting_cet1", 0.0),
                        "credit_losses": scenario_wf.get("credit_losses", 0.0),
                        "ppnr_offset": scenario_wf.get("ppnr_offset", 0.0),
                        "ending_cet1": scenario_wf.get("ending_cet1", 0.0),
                        "top_sensitivity_driver": top_driver,
                    }
                )
        return rows
    except Exception:  # noqa: BLE001
        return _build_scenario_comparison(next(iter(stress_reports.values())))


__all__ = [
    "FILING_OUTPUT_FILES",
    "FilingConfig",
    "FilingGenerator",
    "FilingResult",
]
