"""CECL/CCAR Regulatory Report Export — FR Y-14A Schedule Builder.

Produces compliance-ready submission artifacts for CECL (ASC 326) and
CCAR/DFAST regulatory reporting. Provides:

- QuarterlyLossProjection — single-quarter loss + capital metrics
- CCARSchedule — 9-quarter FR Y-14A-style loss schedule
- CECLDisclosure — ASC 326 methodology/quantitative disclosure block
- RegulatoryExporter — builds all artifacts from existing credit objects

Stdlib-only per ADR-007. No new dependencies.
"""

from __future__ import annotations

import json as _json
from dataclasses import asdict as _asdict
from dataclasses import dataclass, field
from datetime import UTC, datetime
from typing import Any

# ── Schema version constants ──────────────────────────────────────────────────

CECL_SCHEMA_VERSION = "1.0.0"
CCAR_SCHEMA_VERSION = "1.0.0"

# ── Fed reporting field mappings ──────────────────────────────────────────────

#: Maps logical output fields to FR Y-14A schedule references.
FR_Y14A_FIELD_MAP: dict[str, str] = {
    "ecl_estimate": "FR_Y14A_Schedule_H1_Line_1",
    "pd_mean": "FR_Y14A_Schedule_H2_Line_3",
    "lgd": "FR_Y14A_Schedule_H2_Line_7",
    "portfolio_loss_rate": "FR_Y14A_Schedule_H3_Line_2",
    "capital_impact": "FR_Y14A_Schedule_H4_Line_1",
    "horizon_quarters": "FR_Y14A_Schedule_H1_Line_5",
}

#: Maps logical fields to Call Report RC-R categories.
CALL_REPORT_RC_R_MAP: dict[str, str] = {
    "ecl_estimate": "RC_R_Part_I_Line_10",
    "capital_impact": "RC_R_Part_II_Line_4",
    "portfolio_loss_rate": "RC_R_Part_I_Line_12",
}

#: Required fields for ASC 326 CECL disclosures.
ASC_326_REQUIRED_FIELDS: list[str] = [
    "framework",
    "institution_id",
    "reporting_period",
    "asc_326_disclosure",
    "methodology_attestation",
    "quantitative_results",
    "model_governance",
]

# ── FR Y-14A / CECL structured schedule JSON schemas ─────────────────────────

#: Required fields for a CCARSchedule JSON export (FR Y-14A style).
JSON_SCHEMA_FR_Y14: dict[str, list[str]] = {
    "required": [
        "institution_name",
        "scenario",
        "horizon_quarters",
        "quarters",
        "total_projected_loss",
    ]
}

#: Required fields for a CECLDisclosure JSON export (ASC 326 style).
JSON_SCHEMA_CECL: dict[str, list[str]] = {
    "required": [
        "methodology",
        "key_assumptions",
        "macro_sensitivity",
        "allowance_estimate",
    ]
}

# ── Legacy schema validation (export_cecl / export_ccar) ─────────────────────

_CECL_REQUIRED: frozenset[str] = frozenset(ASC_326_REQUIRED_FIELDS)
_CCAR_REQUIRED: frozenset[str] = frozenset(
    [
        "framework",
        "institution_id",
        "reporting_period",
        "fr_y14a_fields",
        "scenario_results",
        "call_report_rc_r",
        "model_governance",
    ]
)


def validate_cecl_schema(export_dict: dict[str, Any]) -> list[str]:
    """Return list of missing ASC 326 required fields (empty = valid)."""
    return [f for f in _CECL_REQUIRED if f not in export_dict]


def validate_ccar_schema(export_dict: dict[str, Any]) -> list[str]:
    """Return list of missing CCAR required fields (empty = valid)."""
    return [f for f in _CCAR_REQUIRED if f not in export_dict]


# ── Quantitative distribution constants ───────────────────────────────────────

# Stress arc: loss distribution across 9 CCAR quarters (peaks Q3-Q5).
_CCAR_STRESS_ARC: tuple[float, ...] = (
    0.06,
    0.09,
    0.13,
    0.16,
    0.15,
    0.13,
    0.11,
    0.09,
    0.08,
)

# Scenario severity multipliers (most-specific key first for substring matching).
_SEVERITY_MULTIPLIERS: dict[str, float] = {
    "severely_adverse": 1.35,
    "adverse": 1.15,
    "baseline": 1.0,
}

# Default segment names when portfolio analytics unavailable.
_DEFAULT_SEGMENTS: tuple[str, ...] = ("commercial", "consumer", "mortgage", "other")

_ALL_SCENARIOS = ("baseline", "adverse", "severely_adverse")


# ── New structured dataclasses ────────────────────────────────────────────────


@dataclass
class QuarterlyLossProjection:
    """Projected loss and capital metrics for a single CCAR quarter.

    Attributes:
        quarter: Quarter label, e.g. ``"Q1-2024"``.
        projected_loss: Fed-scenario-weighted ECL for this quarter.
        ppnr_proxy: Pre-provision net revenue offset (NII heuristic).
        capital_ratio_impact: Estimated Tier-1 capital ratio impact (clamped 0-0.5).
        segment_breakdown: Loss distribution by portfolio segment.
    """

    quarter: str
    projected_loss: float
    ppnr_proxy: float
    capital_ratio_impact: float
    segment_breakdown: dict[str, float]


@dataclass
class CCARSchedule:
    """FR Y-14A-style 9-quarter CCAR loss projection schedule.

    Attributes:
        institution_name: Bank/institution display name.
        scenario: Fed scenario name (baseline / adverse / severely_adverse).
        horizon_quarters: Number of quarters projected (9 for CCAR/DFAST).
        quarters: Ordered list of QuarterlyLossProjection objects.
        total_projected_loss: Sum of projected losses across all quarters.
        total_ead: Total exposure at default of the portfolio.
        generated_at: ISO-8601 UTC generation timestamp.
    """

    institution_name: str
    scenario: str
    horizon_quarters: int
    quarters: list[QuarterlyLossProjection]
    total_projected_loss: float
    total_ead: float
    generated_at: str


@dataclass
class CECLDisclosure:
    """ASC 326 CECL disclosure narrative and quantitative fields.

    Attributes:
        methodology: CECL methodology name (e.g. ``"PD/LGD"``).
        key_assumptions: List of material modelling assumptions.
        macro_sensitivity: Sensitivity of ECL to 1-unit macro shocks.
        qualitative_adjustments: Management overlay descriptions.
        allowance_estimate: Point estimate of allowance for credit losses.
        confidence_lower: Lower bound of 90% confidence interval.
        confidence_upper: Upper bound of 90% confidence interval.
        reporting_date: Reporting period (e.g. ``"2024-Q4"``).
    """

    methodology: str
    key_assumptions: list[str]
    macro_sensitivity: dict[str, float]
    qualitative_adjustments: list[str]
    allowance_estimate: float
    confidence_lower: float
    confidence_upper: float
    reporting_date: str


# ── RegulatoryExporter ────────────────────────────────────────────────────────


@dataclass
class RegulatoryExporter:
    """Produces CECL and CCAR/DFAST submission artifacts.

    Args:
        institution_id: Bank/institution identifier (e.g. RSSD ID or internal).
        reporting_period: Submission period string (e.g. ``"2024-Q4"``).
        methodology: CECL methodology name (``"WARM"``, ``"DCF"``, ``"PD/LGD"``).
    """

    institution_id: str
    reporting_period: str
    methodology: str = "PD/LGD"

    _portfolio_cache: list[dict[str, object]] = field(default_factory=list, repr=False, init=False)

    # ── Structured schedule API ───────────────────────────────────────────────

    def build_fr_y14_schedule(
        self,
        stress_report: Any,
        ecl_result: Any,
        portfolio_analytics: Any = None,
        institution_name: str = "Test Bank",
        base_quarter: str = "Q1-2024",
        ppnr_ratio: float = 0.65,
    ) -> CCARSchedule:
        """Build a FR Y-14A-style 9-quarter CCAR loss projection schedule.

        Distributes total ECL across 9 quarters using an empirical stress arc
        (losses peak Q3-Q5), applies a scenario severity multiplier, and
        computes PPNR proxy and capital ratio impact per quarter.

        Args:
            stress_report: Any object with ``scenario_name`` attribute.
            ecl_result: Any object with ``total_ecl``, ``ead_total``,
                ``confidence_band``, ``lgd`` attributes.
            portfolio_analytics: Optional object providing segment weights via
                ``ecl_by_segment`` or ``segment_breakdown`` attribute.
            institution_name: Display name for the institution.
            base_quarter: Starting quarter label, e.g. ``"Q1-2024"``.
            ppnr_ratio: NII offset ratio applied to each quarter's loss.

        Returns:
            CCARSchedule with 9 QuarterlyLossProjection entries.
        """
        scenario = getattr(stress_report, "scenario_name", "adverse")
        ead_total = float(getattr(ecl_result, "ead_total", 1.0))
        total_ecl = float(getattr(ecl_result, "total_ecl", 0.0))

        # Scenario severity multiplier (most-specific key wins)
        severity_mult = 1.0
        for key, mult in _SEVERITY_MULTIPLIERS.items():
            if key in scenario:
                severity_mult = mult
                break
        adjusted_ecl = total_ecl * severity_mult

        seg_weights = _extract_segment_weights(portfolio_analytics)
        quarter_labels = _generate_quarter_labels(base_quarter, 9)

        quarters: list[QuarterlyLossProjection] = []
        for i, label in enumerate(quarter_labels):
            loss = adjusted_ecl * _CCAR_STRESS_ARC[i]
            ratio = min(loss / max(ead_total, 1.0), 0.5)
            seg_bd = {seg: loss * w for seg, w in seg_weights.items()}
            quarters.append(
                QuarterlyLossProjection(
                    quarter=label,
                    projected_loss=loss,
                    ppnr_proxy=loss * ppnr_ratio,
                    capital_ratio_impact=ratio,
                    segment_breakdown=seg_bd,
                )
            )

        return CCARSchedule(
            institution_name=institution_name,
            scenario=scenario,
            horizon_quarters=9,
            quarters=quarters,
            total_projected_loss=sum(q.projected_loss for q in quarters),
            total_ead=ead_total,
            generated_at=datetime.now(UTC).isoformat(),
        )

    def build_cecl_disclosure(
        self,
        ecl_result: Any,
        portfolio_analytics: Any = None,
        macro_vars: dict[str, float] | None = None,
    ) -> CECLDisclosure:
        """Build an ASC 326 CECL disclosure narrative and quantitative block.

        Args:
            ecl_result: Object with ``total_ecl``, ``confidence_band``,
                ``horizon_quarters``, ``lgd`` attributes.
            portfolio_analytics: Optional, accepted for API symmetry.
            macro_vars: Override for macro sensitivity dict.

        Returns:
            CECLDisclosure ready for JSON export or PDF rendering.
        """
        total_ecl = float(getattr(ecl_result, "total_ecl", 0.0))
        cb = getattr(ecl_result, "confidence_band", (0.0, 0.0))
        horizon = getattr(ecl_result, "horizon_quarters", 9)
        lgd = float(getattr(ecl_result, "lgd", 0.45))

        macro_sensitivity: dict[str, float] = (
            dict(macro_vars)
            if macro_vars
            else {
                "gdp_growth_1pct_shock": -0.12,
                "unemployment_1pct_shock": 0.18,
                "interest_rate_100bps": 0.07,
            }
        )

        return CECLDisclosure(
            methodology=self.methodology,
            key_assumptions=[
                f"Forecast horizon: {horizon} quarters",
                "PD estimated via causal DAG-based model (SR 11-7 compliant)",
                f"LGD fixed at {lgd:.2%}",
                "Macro forecasts sourced from Fed baseline/adverse scenarios",
                "Qualitative adjustments applied per management overlay process",
            ],
            macro_sensitivity=macro_sensitivity,
            qualitative_adjustments=[
                "Management overlay: +5% for emerging sector concentration",
                "Model uncertainty buffer: +2% ECL",
            ],
            allowance_estimate=total_ecl,
            confidence_lower=float(cb[0]),
            confidence_upper=float(cb[1]),
            reporting_date=self.reporting_period,
        )

    def to_json(self, obj: CCARSchedule | CECLDisclosure) -> str:
        """Serialize a CCARSchedule or CECLDisclosure to JSON string.

        Args:
            obj: Dataclass instance to serialize.

        Returns:
            Indented JSON string.
        """
        return _json.dumps(_asdict(obj), indent=2)

    def validate_schema(
        self,
        data: dict[str, Any],
        schema: dict[str, list[str]] | None = None,
    ) -> list[str]:
        """Validate data dict against required schema fields.

        Args:
            data: Dict to validate (e.g. ``dataclasses.asdict(schedule)``).
            schema: Schema dict with ``"required"`` key. Defaults to
                JSON_SCHEMA_FR_Y14.

        Returns:
            List of missing field names. Empty list = valid.
        """
        if schema is None:
            schema = JSON_SCHEMA_FR_Y14
        required = schema.get("required", [])
        return [f for f in required if f not in data]

    # ── Legacy export API ─────────────────────────────────────────────────────

    def export_cecl(self, format: str = "json") -> dict[str, Any] | str:  # noqa: A002
        """Export a CECL / ASC 326 submission artifact.

        Args:
            format: ``"json"`` returns a dict; ``"markdown"`` returns a str.

        Raises:
            ValueError: If format is not ``"json"`` or ``"markdown"``.
        """
        if format not in ("json", "markdown"):
            msg = f"Unknown format '{format}'. Must be 'json' or 'markdown'."
            raise ValueError(msg)

        ecl_result = self._run_cecl()

        data: dict[str, Any] = {
            "framework": "CECL",
            "schema_version": CECL_SCHEMA_VERSION,
            "institution_id": self.institution_id,
            "reporting_period": self.reporting_period,
            "generated_at": datetime.now(UTC).isoformat(),
            "asc_326_disclosure": (
                f"This report satisfies ASC 326-20 measurement requirements "
                f"for financial period {self.reporting_period}. "
                f"Expected credit losses are estimated using the {self.methodology} "
                f"methodology with causal probability-of-default modelling."
            ),
            "methodology_attestation": (
                f"Management attests that the {self.methodology} methodology "
                f"applied herein is appropriate for the institution's credit portfolio "
                f"as of {self.reporting_period}. Model validation was performed in "
                f"accordance with SR 11-7 supervisory guidance."
            ),
            "quantitative_results": {
                "ecl_estimate": ecl_result["total_ecl"],
                "confidence_band": ecl_result["confidence_band"],
                "segments": ecl_result["ecl_by_segment"],
                "horizon_quarters": ecl_result["horizon_quarters"],
                "pd_mean": ecl_result["pd_mean"],
                "lgd": ecl_result["lgd"],
                "ead_total": ecl_result["ead_total"],
            },
            "model_governance": self._model_governance_block(),
            "fr_y14a_field_map": FR_Y14A_FIELD_MAP,
        }

        if format == "markdown":
            return self._to_markdown(data, "CECL")
        return data

    def export_ccar(
        self,
        format: str = "json",  # noqa: A002
        scenarios: list[str] | None = None,
    ) -> dict[str, Any] | str:
        """Export a CCAR/DFAST submission artifact.

        Args:
            format: ``"json"`` returns a dict; ``"markdown"`` returns a str.
            scenarios: Subset of scenario names to include.

        Raises:
            ValueError: If format is invalid.
        """
        if format not in ("json", "markdown"):
            msg = f"Unknown format '{format}'. Must be 'json' or 'markdown'."
            raise ValueError(msg)

        requested = list(scenarios) if scenarios else list(_ALL_SCENARIOS)
        scenario_results = self._run_stress_scenarios(requested)

        data: dict[str, Any] = {
            "framework": "CCAR",
            "schema_version": CCAR_SCHEMA_VERSION,
            "institution_id": self.institution_id,
            "reporting_period": self.reporting_period,
            "generated_at": datetime.now(UTC).isoformat(),
            "fr_y14a_fields": {
                "schedule_mappings": FR_Y14A_FIELD_MAP,
                "submission_period": self.reporting_period,
                "institution_id": self.institution_id,
                "horizon_quarters": 9,
            },
            "scenario_results": scenario_results,
            "call_report_rc_r": {
                "schedule": "RC-R",
                "field_mappings": CALL_REPORT_RC_R_MAP,
                "reporting_period": self.reporting_period,
            },
            "model_governance": self._model_governance_block(),
        }

        if format == "markdown":
            return self._to_markdown(data, "CCAR")
        return data

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

    def _get_portfolio(self) -> list[dict[str, object]]:
        if not self._portfolio_cache:
            self._portfolio_cache = _build_synthetic_portfolio(200, seed=42)
        return self._portfolio_cache

    def _run_cecl(self) -> dict[str, Any]:
        from orxaq.credit.causal_pd import CausalPDModel
        from orxaq.credit.cecl import CECLEngine
        from orxaq.kernel import WorldDAG

        dag = WorldDAG()
        _load_credit_ontology(dag)
        pd_model = CausalPDModel(dag)
        portfolio = self._get_portfolio()
        pd_model.fit(portfolio)  # type: ignore[arg-type]

        engine = CECLEngine(pd_model, dag)
        result = engine.expected_credit_loss(portfolio)  # type: ignore[arg-type]

        return {
            "total_ecl": result.total_ecl,
            "ecl_by_segment": result.ecl_by_segment,
            "horizon_quarters": result.horizon_quarters,
            "confidence_band": list(result.confidence_band),
            "pd_mean": result.pd_mean,
            "lgd": result.lgd,
            "ead_total": result.ead_total,
        }

    def _run_stress_scenarios(self, scenario_names: list[str]) -> list[dict[str, Any]]:
        from orxaq.credit.causal_pd import CausalPDModel
        from orxaq.credit.cecl import CECLEngine
        from orxaq.credit.portfolio import LoanExposure
        from orxaq.credit.stress_report import FED_SCENARIOS, run_stress_test
        from orxaq.kernel import WorldDAG

        dag = WorldDAG()
        _load_credit_ontology(dag)
        pd_model = CausalPDModel(dag)
        portfolio = self._get_portfolio()
        pd_model.fit(portfolio)  # type: ignore[arg-type]
        cecl_engine = CECLEngine(pd_model, dag)

        exposures = [
            LoanExposure(
                loan_id=str(i),
                pd=pd_model.predict_pd(row),  # type: ignore[arg-type]
                lgd=float(row.get("lgd", 0.45)),  # type: ignore[arg-type]
                ead=float(row.get("loan_amount", 100_000.0)),  # type: ignore[arg-type]
                sector=str(row.get("sector", "corporate")),
                vintage="2024",
            )
            for i, row in enumerate(portfolio)
        ]

        results = []
        for name in scenario_names:
            if name not in FED_SCENARIOS:
                continue
            report = run_stress_test(
                portfolio=exposures,
                scenarios={name: FED_SCENARIOS[name]},
                pd_model=pd_model,
                cecl_engine=cecl_engine,
                scenario_name=name,
            )
            results.append(
                {
                    "scenario_name": report.scenario_name,
                    "ecl": sum(report.quarterly_ecl),
                    "pd_mean": _mean_quarterly_ecl_to_pd_proxy(report.quarterly_ecl),
                    "migration_matrices_count": len(report.migration_matrices),
                    "portfolio_loss_rate": report.portfolio_loss_rate,
                    "capital_impact": report.capital_impact,
                }
            )
        return results

    def _model_governance_block(self) -> dict[str, str]:
        return {
            "validation_summary": (
                "Model validated against back-test data per SR 11-7. "
                "Causal DAG topology reviewed by Model Risk Management."
            ),
            "last_validated": self.reporting_period,
            "attestation_officer": "Chief Risk Officer (Placeholder)",
            "model_risk_tier": "Tier 1 — High Material Impact",
            "next_review_date": _next_review_quarter(self.reporting_period),
        }

    @staticmethod
    def _to_markdown(data: dict[str, Any], framework: str) -> str:
        lines: list[str] = []

        if framework == "CECL":
            lines.append("# CECL / ASC 326 Regulatory Submission")
        else:
            lines.append("# CCAR/DFAST Regulatory Stress Test Submission")
        lines.append("")

        lines.append("## Required Regulatory Fields")
        lines.append("")
        lines.append("| Field | Value |")
        lines.append("|-------|-------|")
        lines.append(f"| Institution ID | `{data.get('institution_id', '')}` |")
        lines.append(f"| Reporting Period | {data.get('reporting_period', '')} |")
        lines.append(f"| Framework | {data.get('framework', '')} |")
        lines.append(f"| Schema Version | {data.get('schema_version', '')} |")
        lines.append(f"| Generated At | {data.get('generated_at', '')} |")
        lines.append("")

        if framework == "CECL":
            lines.append("## Methodology")
            lines.append("")
            lines.append(data.get("asc_326_disclosure", ""))
            lines.append("")
            lines.append("### Attestation")
            lines.append("")
            lines.append(data.get("methodology_attestation", ""))
            lines.append("")

            qr = data.get("quantitative_results", {})
            lines.append("## Quantitative Results")
            lines.append("")
            lines.append("| Metric | Value |")
            lines.append("|--------|-------|")
            lines.append(f"| ECL Estimate | {qr.get('ecl_estimate', 0.0):,.2f} |")
            cb = qr.get("confidence_band", [0.0, 0.0])
            lines.append(f"| Confidence Band (90%) | [{cb[0]:,.2f}, {cb[1]:,.2f}] |")
            lines.append(f"| Horizon (Quarters) | {qr.get('horizon_quarters', 0)} |")
            lines.append(f"| PD Mean | {qr.get('pd_mean', 0.0):.4f} |")
            lines.append(f"| LGD | {qr.get('lgd', 0.0):.4f} |")
            lines.append(f"| EAD Total | {qr.get('ead_total', 0.0):,.2f} |")
            lines.append("")

            segs = qr.get("segments", {})
            if segs:
                lines.append("### ECL by Segment")
                lines.append("")
                lines.append("| Segment | ECL |")
                lines.append("|---------|-----|")
                for seg, ecl_val in segs.items():
                    lines.append(f"| {seg} | {ecl_val:,.2f} |")
                lines.append("")

        if framework == "CCAR":
            lines.append("## Methodology")
            lines.append("")
            lines.append(
                "Stress testing performed per CCAR/DFAST supervisory guidance. "
                "ECL estimated using PD/LGD methodology with Fed-prescribed macro scenarios."
            )
            lines.append("")

            lines.append("## Scenario Results")
            lines.append("")
            lines.append("| Scenario | ECL | PD Proxy | Loss Rate | Capital Impact | Matrices |")
            lines.append("|----------|-----|----------|-----------|----------------|----------|")
            for sr in data.get("scenario_results", []):
                lines.append(
                    f"| {sr.get('scenario_name', '')} "
                    f"| {sr.get('ecl', 0.0):,.2f} "
                    f"| {sr.get('pd_mean', 0.0):.4f} "
                    f"| {sr.get('portfolio_loss_rate', 0.0):.4f} "
                    f"| {sr.get('capital_impact', 0.0):.4f} "
                    f"| {sr.get('migration_matrices_count', 0)} |"
                )
            lines.append("")

            lines.append("## FR Y-14A Field Mappings")
            lines.append("")
            lines.append("| Logical Field | FR Y-14A Reference |")
            lines.append("|---------------|-------------------|")
            for logical, ref in FR_Y14A_FIELD_MAP.items():
                lines.append(f"| {logical} | {ref} |")
            lines.append("")

        lines.append("## Model Governance Attestation")
        lines.append("")
        mg = data.get("model_governance", {})
        lines.append(f"**Validation Summary**: {mg.get('validation_summary', '')}")
        lines.append("")
        lines.append(f"**Last Validated**: {mg.get('last_validated', '')}")
        lines.append("")
        lines.append(f"**Attestation Officer**: {mg.get('attestation_officer', '')}")
        lines.append("")
        lines.append(f"**Model Risk Tier**: {mg.get('model_risk_tier', '')}")
        lines.append("")
        lines.append(f"**Next Review Date**: {mg.get('next_review_date', '')}")
        lines.append("")
        lines.append("---")
        lines.append(
            f"*Generated by Orxaq Regulatory Export v{CECL_SCHEMA_VERSION} — "
            f"Confidential / Supervisory Use Only*"
        )

        return "\n".join(lines)


# ── Module-level helpers ──────────────────────────────────────────────────────


def _extract_segment_weights(portfolio_analytics: Any) -> dict[str, float]:
    """Extract segment weights from analytics object or use equal defaults."""
    segs: dict[str, float] | None = None
    if portfolio_analytics is not None:
        segs = getattr(portfolio_analytics, "ecl_by_segment", None)
        if segs is None:
            segs = getattr(portfolio_analytics, "segment_breakdown", None)
    if segs:
        total = sum(segs.values())
        if total > 0:
            return {k: v / total for k, v in segs.items()}
    n = len(_DEFAULT_SEGMENTS)
    return {seg: 1.0 / n for seg in _DEFAULT_SEGMENTS}


def _generate_quarter_labels(base_quarter: str, count: int) -> list[str]:
    """Generate ``count`` quarter labels starting from ``base_quarter``.

    Args:
        base_quarter: Starting quarter string, e.g. ``"Q1-2024"``.
        count: Number of labels to generate.
    """
    try:
        q_part, year_part = base_quarter.split("-")
        q = int(q_part[1:])
        year = int(year_part)
    except (ValueError, IndexError):
        q, year = 1, 2024
    labels: list[str] = []
    for _ in range(count):
        labels.append(f"Q{q}-{year}")
        q += 1
        if q > 4:
            q = 1
            year += 1
    return labels


def _build_synthetic_portfolio(n: int, seed: int = 42) -> list[dict[str, object]]:
    """Build a deterministic synthetic loan portfolio for demo/test use."""
    import random

    rng = random.Random(seed)
    portfolio = []
    for i in range(n):
        portfolio.append(
            {
                "loan_id": float(i),
                "loan_amount": rng.uniform(50_000, 500_000),
                "current_balance": rng.uniform(40_000, 490_000),
                "credit_score": rng.uniform(550, 800),
                "debt_to_income": rng.uniform(0.15, 0.55),
                "unemployment_rate": rng.uniform(0.03, 0.12),
                "gdp_growth": rng.uniform(-0.02, 0.04),
                "ltv_ratio": rng.uniform(0.5, 0.95),
                "lgd": rng.uniform(0.35, 0.60),
                "_segment": rng.choice(["retail", "commercial", "residential"]),
                "default_flag": float(rng.random() < 0.05),
            }
        )
    return portfolio


def _load_credit_ontology(dag: Any) -> None:
    """Load credit ontology nodes/edges into dag (best-effort)."""
    try:
        from orxaq.credit.ontology import CREDIT_VARIABLES

        for var in CREDIT_VARIABLES:
            try:
                dag.add_node(var, domain="credit")
            except Exception:  # noqa: BLE001
                pass
        _edges = [
            ("unemployment_rate", "default_flag", "macro_to_default"),
            ("gdp_growth", "default_flag", "macro_to_default"),
            ("credit_score", "default_flag", "borrower_to_default"),
            ("debt_to_income", "default_flag", "borrower_to_default"),
            ("ltv_ratio", "default_flag", "collateral_to_default"),
        ]
        for src, tgt, mech in _edges:
            try:
                dag.add_edge(src, tgt, mechanism=mech)
            except Exception:  # noqa: BLE001
                pass
    except Exception:  # noqa: BLE001
        pass


def _mean_quarterly_ecl_to_pd_proxy(quarterly_ecl: list[float]) -> float:
    """Derive a PD proxy from quarterly ECL values."""
    if not quarterly_ecl:
        return 0.0
    max_ecl = max(quarterly_ecl)
    if max_ecl == 0.0:
        return 0.0
    return sum(quarterly_ecl) / (len(quarterly_ecl) * max_ecl)


def _next_review_quarter(reporting_period: str) -> str:
    """Advance a YYYY-QN reporting period by one quarter."""
    try:
        year_str, q_str = reporting_period.split("-Q")
        year, q = int(year_str), int(q_str)
        q += 1
        if q > 4:
            q = 1
            year += 1
        return f"{year}-Q{q}"
    except (ValueError, AttributeError):
        return reporting_period


__all__ = [
    "ASC_326_REQUIRED_FIELDS",
    "CALL_REPORT_RC_R_MAP",
    "CECL_SCHEMA_VERSION",
    "CCAR_SCHEMA_VERSION",
    "CCARSchedule",
    "CECLDisclosure",
    "FR_Y14A_FIELD_MAP",
    "JSON_SCHEMA_CECL",
    "JSON_SCHEMA_FR_Y14",
    "QuarterlyLossProjection",
    "RegulatoryExporter",
    "validate_cecl_schema",
    "validate_ccar_schema",
]
