"""Integration gateway — ``orxaq run`` end-to-end pipeline entry point.

Composes all Ring 3 credit/regulatory stages into a single callable:

    RunConfig → run_pipeline → RunResult

No new algorithms.  Pure composition of existing engines per ADR-007.
Stdlib-only.
"""

from __future__ import annotations

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

# ── Config ─────────────────────────────────────────────────────────────────


@dataclass
class RunConfig:
    """Configuration for a full CECL/CCAR filing pipeline run.

    Args:
        institution_id: Unique identifier for the regulated institution.
        period: Reporting period label (e.g. ``"2026Q1"``).
        output_dir: Directory where filing package will be written.
        n_loans: Number of synthetic loans to generate.
        horizon_quarters: CCAR forecast horizon in quarters.
        seed: Random seed for reproducible synthetic data.
        scenario_names: Scenario keys to include in stress analysis.
        include_fair_lending: Whether to run FairLendingAnalyzer.
    """

    institution_id: str
    period: str = "2026Q1"
    output_dir: str = "./filing"
    n_loans: int = 200
    horizon_quarters: int = 9
    seed: int = 42
    scenario_names: list[str] = field(
        default_factory=lambda: ["baseline", "adverse", "severely_adverse"]
    )
    include_fair_lending: bool = True
    input_file: str | None = None  # path to FR Y-14M loan tape CSV/JSON


# ── Result ─────────────────────────────────────────────────────────────────


@dataclass
class RunResult:
    """Result of a complete filing pipeline run.

    Args:
        institution_id: Institution identifier from config.
        period: Reporting period from config.
        output_dir: Absolute path to the output directory.
        files: List of output file names produced.
        duration_s: Wall-clock seconds the pipeline took.
        audit_entries: Number of audit log entries recorded.
        ecl_total: CECL expected credit loss total (currency units).
        stress_summaries: Scenario name → list of row dicts from CSV.
        fair_lending_flags: List of flagged fair-lending findings.
    """

    institution_id: str
    period: str
    output_dir: str
    files: list[str]
    duration_s: float
    audit_entries: int
    ecl_total: float
    stress_summaries: dict[str, Any]
    fair_lending_flags: list[str]


# ── Pipeline ────────────────────────────────────────────────────────────────


def run_pipeline(config: RunConfig) -> RunResult:
    """Run the full CECL/CCAR filing pipeline for the given configuration.

    Delegates to :class:`orxaq.credit.filing.FilingGenerator` for the heavy
    lifting, then augments the result with ECL total (read from the generated
    ``cecl_disclosure.json``), stress summaries (from
    ``scenario_comparison.csv``), and optional fair-lending flags.

    Args:
        config: Pipeline configuration.

    Returns:
        :class:`RunResult` with output metadata and regulatory metrics.
    """
    from orxaq.credit.filing import FilingConfig, FilingGenerator  # noqa: PLC0415

    portfolio_data = None
    if config.input_file is not None:
        portfolio_data = _load_portfolio_file(config.input_file)

    filing_cfg = FilingConfig(
        institution_id=config.institution_id,
        period=config.period,
        output_dir=config.output_dir,
        seed=config.seed,
        n_samples=config.n_loans,
        horizon_quarters=config.horizon_quarters,
        portfolio_data=portfolio_data,
    )
    filing_result = FilingGenerator(filing_cfg).generate()

    out = Path(config.output_dir)

    ecl_total = _read_ecl_total(out)
    stress_summaries = _read_stress_summaries(out)

    fair_lending_flags: list[str] = []
    if config.include_fair_lending:
        fair_lending_flags = _run_fair_lending()

    return RunResult(
        institution_id=config.institution_id,
        period=config.period,
        output_dir=filing_result.output_dir,
        files=filing_result.files,
        duration_s=filing_result.duration_seconds,
        audit_entries=filing_result.audit_entries_count,
        ecl_total=ecl_total,
        stress_summaries=stress_summaries,
        fair_lending_flags=fair_lending_flags,
    )


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

# FR Y-14M → internal field name mapping (fico_score → credit_score etc.)
_FR_Y14M_FIELD_MAP: dict[str, str] = {
    "fico_score": "credit_score",
    "current_balance": "current_balance",
    "interest_rate": "interest_rate",
    "ltv": "ltv",
    "dti": "dti",
    "pd": "pd",
    "lgd": "lgd",
    "ead": "ead",
}


def _load_portfolio_file(path: str) -> list[dict[str, Any]]:
    """Load a bank loan tape CSV or JSON into pipeline-compatible dicts.

    Applies FR Y-14M → internal field name mapping.  Non-numeric values
    are silently dropped (the pipeline only uses float features).
    """
    p = Path(path)
    if not p.exists():
        raise FileNotFoundError(f"Input file not found: {path}")

    rows: list[dict[str, Any]] = []
    if p.suffix.lower() == ".csv":
        with open(p, encoding="utf-8", newline="") as fh:
            reader = csv.DictReader(fh)
            for row in reader:
                rows.append(dict(row))
    else:
        with open(p, encoding="utf-8") as fh:
            data = json.load(fh)
        rows = data if isinstance(data, list) else data.get("loans", [])

    # Rename fields and coerce to float
    result: list[dict[str, Any]] = []
    for raw in rows:
        rec: dict[str, Any] = {}
        for src, val in raw.items():
            dst = _FR_Y14M_FIELD_MAP.get(src, src)
            try:
                rec[dst] = float(val)
            except (ValueError, TypeError):
                rec[dst] = val  # keep non-numeric as-is (strings etc.)
        result.append(rec)
    return result


def _read_ecl_total(output_dir: Path) -> float:
    """Read ECL total from ``cecl_disclosure.json`` written by FilingGenerator."""
    path = output_dir / "cecl_disclosure.json"
    if not path.exists():
        return 0.0
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return float(data.get("quantitative_results", {}).get("ecl_estimate", 0.0))
    except (json.JSONDecodeError, TypeError, ValueError, OSError):
        return 0.0


def _read_stress_summaries(output_dir: Path) -> dict[str, list[dict[str, Any]]]:
    """Build per-scenario summaries from ``scenario_comparison.csv``."""
    path = output_dir / "scenario_comparison.csv"
    if not path.exists():
        return {}
    summaries: dict[str, list[dict[str, Any]]] = {}
    try:
        with path.open(newline="", encoding="utf-8") as fh:
            reader = csv.DictReader(fh)
            for row in reader:
                scenario = str(row.get("scenario", "unknown"))
                summaries.setdefault(scenario, []).append(dict(row))
    except (OSError, csv.Error):
        return {}
    return summaries


def _run_fair_lending() -> list[str]:
    """Run FairLendingAnalyzer and return a list of flag strings."""
    from orxaq.credit.fair_lending import FairLendingAnalyzer  # noqa: PLC0415
    from orxaq.credit.ontology import build_credit_ontology  # noqa: PLC0415

    dag = build_credit_ontology()
    analyzer = FairLendingAnalyzer(dag)
    flags: list[str] = []
    try:
        decomp = analyzer.decompose_effect("age", "default_flag")
        if decomp.direct_effect > 0:
            flags.append(f"age\u2192default_flag direct_effect={decomp.direct_effect:.4f}")
    except Exception:  # noqa: BLE001
        pass
    return flags


__all__ = [
    "RunConfig",
    "RunResult",
    "run_pipeline",
]
