"""CECL/CCAR Regulatory Report Export Module.

Produces regulatory-submission-ready tabular outputs for:
- FR Y-14A Schedule H.1 (ACL by segment, CECL)
- CCAR 9-quarter ECL projections with confidence intervals
- Scenario comparison table (baseline / adverse / severely adverse)

Stdlib-only (Tier 0 compliant) — no pandas, no openpyxl.
"""

from __future__ import annotations

import csv
import datetime
import io
import json
from dataclasses import dataclass, field

__all__ = [
    "RegulatoryPackage",
    "ScheduleH1Builder",
    "CCARProjectionBuilder",
    "ScenarioComparisonTable",
    "RegulatoryExporter",
    "build_regulatory_package",
]

# CCAR/DFAST mandated horizon — 9 quarters
_CCAR_QUARTERS = 9


def _now_iso() -> str:
    """Return current UTC time as ISO-8601 string."""
    return datetime.datetime.now(datetime.UTC).isoformat()


@dataclass(frozen=True)
class RegulatoryPackage:
    """Bundle of regulatory outputs for a single submission period.

    Attributes:
        as_of_date: Reporting as-of date (YYYY-MM-DD).
        institution_id: Regulatory institution identifier.
        submission_period: E.g. '2025Q4'.
        schedule_h1: FR Y-14A Schedule H.1 rows (list of dicts).
        ccar_projection: 9-quarter ECL projection rows (list of dicts).
        scenario_comparison: Scenario comparison table rows (list of dicts).
        generated_at: ISO-8601 UTC timestamp of package generation.
    """

    as_of_date: str
    institution_id: str
    submission_period: str
    schedule_h1: list[dict]
    ccar_projection: list[dict]
    scenario_comparison: list[dict]
    generated_at: str = field(default_factory=_now_iso)


class ScheduleH1Builder:
    """Build FR Y-14A Schedule H.1 — ACL by segment table.

    Accepts any CECLResult-like object with:
        .segments (dict[str, dict]) — per-segment metrics including
            'ead', 'pd', 'lgd', 'ecl' keys.  If only a flat
            dict[str, float] (ecl per segment) is given, EAD/PD/LGD
            fall back to object-level attributes.
        .ecl (float) — total ECL (used if per-segment ecl not in segments).
    """

    def __init__(self, cecl_result: object) -> None:
        self._result = cecl_result

    def build(self) -> list[dict]:
        """Return list of dicts with columns: segment, ead, pd, lgd, ecl, acl_coverage_pct."""
        result = self._result
        segments: dict = getattr(result, "segments", {}) or {}

        # Top-level fallback values
        total_ecl: float = float(getattr(result, "ecl", 0.0))
        global_pd: float = float(getattr(result, "pd", 0.0))
        global_lgd: float = float(getattr(result, "lgd", 0.45))
        global_ead: float = float(getattr(result, "ead", 0.0))

        rows: list[dict] = []
        for seg_name, seg_data in segments.items():
            if isinstance(seg_data, dict):
                ead = float(seg_data.get("ead", global_ead))
                pd_val = float(seg_data.get("pd", global_pd))
                lgd = float(seg_data.get("lgd", global_lgd))
                ecl = float(seg_data.get("ecl", total_ecl))
            else:
                # seg_data is just an ECL float
                ecl = float(seg_data)
                ead = global_ead
                pd_val = global_pd
                lgd = global_lgd

            acl_coverage_pct = (ecl / ead * 100.0) if ead > 0.0 else 0.0
            rows.append(
                {
                    "segment": seg_name,
                    "ead": ead,
                    "pd": pd_val,
                    "lgd": lgd,
                    "ecl": ecl,
                    "acl_coverage_pct": acl_coverage_pct,
                }
            )

        return rows


class CCARProjectionBuilder:
    """Build CCAR 9-quarter ECL projection table.

    Accepts any StressTestReport-like object with:
        .quarterly_ecl (list[float]) — ECL per quarter.
        .confidence_intervals (list[tuple[float, float]] or dict) —
            per-quarter CI pairs.  If a dict, the 'total_ecl' key is
            used and broadcast across all quarters.
    """

    def __init__(self, stress_report: object) -> None:
        self._report = stress_report

    def build(self) -> list[dict]:
        """Return exactly 9 dicts each with: quarter, ecl, ci_lower, ci_upper."""
        report = self._report
        quarterly_ecl: list[float] = list(getattr(report, "quarterly_ecl", []))
        ci_raw = getattr(report, "confidence_intervals", [])

        # Normalise confidence intervals to list[tuple[float, float]]
        ci_list: list[tuple[float, float]]
        if isinstance(ci_raw, dict):
            # Broadcast the total_ecl CI across all quarters
            total_ci: tuple[float, float] = ci_raw.get("total_ecl", (0.0, 0.0))  # type: ignore[assignment]
            ci_list = [total_ci] * _CCAR_QUARTERS
        else:
            ci_list = [tuple(pair) for pair in ci_raw]  # type: ignore[misc]

        # Pad or truncate to exactly 9 quarters
        if len(quarterly_ecl) < _CCAR_QUARTERS:
            last = quarterly_ecl[-1] if quarterly_ecl else 0.0
            quarterly_ecl = quarterly_ecl + [last] * (_CCAR_QUARTERS - len(quarterly_ecl))
        else:
            quarterly_ecl = quarterly_ecl[:_CCAR_QUARTERS]

        if len(ci_list) < _CCAR_QUARTERS:
            last_ci = ci_list[-1] if ci_list else (0.0, 0.0)
            ci_list = ci_list + [last_ci] * (_CCAR_QUARTERS - len(ci_list))
        else:
            ci_list = ci_list[:_CCAR_QUARTERS]

        rows: list[dict] = []
        for i in range(_CCAR_QUARTERS):
            ci_lower, ci_upper = ci_list[i]
            rows.append(
                {
                    "quarter": f"Q{i + 1}",
                    "ecl": quarterly_ecl[i],
                    "ci_lower": float(ci_lower),
                    "ci_upper": float(ci_upper),
                }
            )

        return rows


class ScenarioComparisonTable:
    """Build scenario comparison table across multiple stress scenarios.

    Accepts a dict mapping scenario name (str) to a StressTestReport-like
    object with:
        .quarterly_ecl (list[float])
    """

    def __init__(self, scenario_reports: dict[str, object]) -> None:
        self._reports = scenario_reports

    def build(self) -> list[dict]:
        """Return one row per scenario with: scenario, total_ecl, peak_quarterly_ecl,
        avg_quarterly_ecl, quarters."""
        rows: list[dict] = []
        for name, report in self._reports.items():
            quarterly: list[float] = list(getattr(report, "quarterly_ecl", []))
            total = sum(quarterly)
            peak = max(quarterly) if quarterly else 0.0
            avg = total / len(quarterly) if quarterly else 0.0
            rows.append(
                {
                    "scenario": name,
                    "total_ecl": total,
                    "peak_quarterly_ecl": peak,
                    "avg_quarterly_ecl": avg,
                    "quarters": len(quarterly),
                }
            )
        return rows


class RegulatoryExporter:
    """Export regulatory table rows to CSV, JSON, and Excel-compatible dict."""

    @staticmethod
    def to_csv(rows: list[dict]) -> str:
        """Return CSV string with header row."""
        if not rows:
            return ""
        buf = io.StringIO()
        writer = csv.DictWriter(buf, fieldnames=list(rows[0].keys()))
        writer.writeheader()
        for row in rows:
            writer.writerow(row)
        return buf.getvalue()

    @staticmethod
    def to_json(rows: list[dict]) -> str:
        """Return JSON array string."""
        return json.dumps(rows, indent=2, default=str)

    @staticmethod
    def to_excel_dict(rows: list[dict]) -> dict[str, list]:
        """Return column-oriented dict (openpyxl/pandas compatible, no import required).

        Keys are column names; values are lists of equal length.
        """
        if not rows:
            return {}
        keys = list(rows[0].keys())
        return {k: [row.get(k) for row in rows] for k in keys}


def build_regulatory_package(
    cecl_result: object,
    stress_reports: dict[str, object],
    as_of_date: str,
    institution_id: str,
    submission_period: str,
) -> RegulatoryPackage:
    """Convenience function: compose all builders into a RegulatoryPackage.

    Args:
        cecl_result: Any CECLResult-like object (duck-typed).
        stress_reports: Mapping of scenario name -> StressTestReport-like.
        as_of_date: Reporting date (YYYY-MM-DD).
        institution_id: Regulatory institution ID.
        submission_period: E.g. '2025Q4'.

    Returns:
        Populated RegulatoryPackage ready for export.
    """
    schedule_h1 = ScheduleH1Builder(cecl_result).build()

    # Use the first stress report for the CCAR projection (adverse by convention)
    projection_report = (
        stress_reports.get("adverse")
        or stress_reports.get("baseline")
        or (next(iter(stress_reports.values())) if stress_reports else None)
    )
    ccar_projection = CCARProjectionBuilder(projection_report).build() if projection_report else []
    scenario_comparison = ScenarioComparisonTable(stress_reports).build()

    return RegulatoryPackage(
        as_of_date=as_of_date,
        institution_id=institution_id,
        submission_period=submission_period,
        schedule_h1=schedule_h1,
        ccar_projection=ccar_projection,
        scenario_comparison=scenario_comparison,
    )
