"""End-to-end tests for portfolio ingest → CECL pipeline integration."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from orxaq.fabric.portfolio_ingest import (
    FR_Y14M_CONFIG,
    IngestResult,
    PortfolioIngestConfig,
    ingest_portfolio,
)
from orxaq.orchestration.run import RunConfig, run_pipeline

FIXTURE_CSV = Path(__file__).parent.parent / "fixtures" / "fr_y14m_sample.csv"
FIXTURE_JSON = Path(__file__).parent.parent / "fixtures" / "fr_y14m_sample.json"


@pytest.fixture(scope="module")
def json_fixture(tmp_path_factory):
    """Convert CSV fixture to JSON for JSON-path testing."""
    import csv

    p = tmp_path_factory.mktemp("json_fix") / "loans.json"
    rows = []
    with open(FIXTURE_CSV, encoding="utf-8", newline="") as fh:
        for row in csv.DictReader(fh):
            rows.append(dict(row))
    p.write_text(json.dumps(rows), encoding="utf-8")
    return p


# ── Row count and basic structure ─────────────────────────────


class TestFixtureCsvIngest:
    def test_returns_20_loan_exposures(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        assert len(result.loans) == 20

    def test_row_count_matches(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        assert result.row_count == 20

    def test_all_loan_ids_unique(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        ids = [loan.loan_id for loan in result.loans]
        assert len(set(ids)) == 20

    def test_fico_scores_in_valid_range(self) -> None:
        """fico_score in fixture → passed through bounds validation."""
        result = ingest_portfolio(FIXTURE_CSV)
        # All loans parsed without bounds error → test passes
        assert result.error_count == 0

    def test_interest_rates_non_negative(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        assert result.error_count == 0  # all interest_rate values in [0, 0.5]

    def test_ltv_in_bounds(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        assert result.error_count == 0  # all ltv values in [0, 2]

    def test_ead_positive_from_current_balance(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        assert all(loan.ead > 0 for loan in result.loans)

    def test_vintage_derived_from_origination_date(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        non_unknown = [loan for loan in result.loans if loan.vintage != "unknown"]
        assert len(non_unknown) == 20  # all rows have origination_date

    def test_vintage_format(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        for loan in result.loans:
            if loan.vintage != "unknown":
                year, q = loan.vintage.split("Q")
                assert 2015 <= int(year) <= 2030
                assert int(q) in (1, 2, 3, 4)

    def test_quality_gates_pass_on_clean_fixture(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV, quality_threshold=0.9)
        assert result.error_count == 0

    def test_audit_trail_entry_written(self) -> None:
        log: list = []
        ingest_portfolio(FIXTURE_CSV, audit_log=log)
        assert log[0]["action"] == "portfolio_ingest"
        assert log[0]["details"]["row_count"] == 20

    def test_error_count_zero(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        assert result.error_count == 0

    def test_warnings_empty_or_only_missing_optional(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        # Warnings only if some optional cols absent — not an error
        assert isinstance(result.warnings, list)


# ── JSON fixture ──────────────────────────────────────────────


class TestJsonFixtureIngest:
    def test_json_returns_same_count(self, json_fixture: Path) -> None:
        result = ingest_portfolio(json_fixture)
        assert len(result.loans) == 20

    def test_json_row_count(self, json_fixture: Path) -> None:
        result = ingest_portfolio(json_fixture)
        assert result.row_count == 20


# ── Custom field mapping ──────────────────────────────────────


class TestCustomFieldMapping:
    def test_column_remap_via_custom_config(self, tmp_path: Path) -> None:
        """Custom field_map works with non-standard column names."""
        p = tmp_path / "custom.csv"
        p.write_text(
            "acc_num,rate,score,ratio_ltv,ratio_dti\nACN001,0.045,730,0.75,0.32\n",
            encoding="utf-8",
        )
        cfg = PortfolioIngestConfig(
            field_map={
                "acc_num": "loan_id",
                "rate": "interest_rate",
                "score": "fico_score",
                "ratio_ltv": "ltv",
                "ratio_dti": "dti",
            },
            required_fields=["acc_num", "rate", "score", "ratio_ltv", "ratio_dti"],
            optional_fields=[],
            bounds={"score": (300.0, 850.0)},
        )
        result = ingest_portfolio(p, cfg, run_quality=False)
        assert len(result.loans) == 1
        assert result.loans[0].loan_id == "ACN001"


# ── Default FR_Y14M_CONFIG ────────────────────────────────────


class TestFRY14MConfigDefaults:
    def test_default_config_works(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV, FR_Y14M_CONFIG)
        assert len(result.loans) == 20

    def test_no_config_arg_uses_default(self) -> None:
        result = ingest_portfolio(FIXTURE_CSV)
        assert isinstance(result, IngestResult)


# ── Full pipeline E2E ─────────────────────────────────────────


class TestFullPipelineFromFile:
    @pytest.fixture(scope="class")
    def pipeline_result(self, tmp_path_factory):
        out = tmp_path_factory.mktemp("e2e_pipeline")
        cfg = RunConfig(
            institution_id="E2E_BANK",
            period="2026Q1",
            output_dir=str(out),
            n_loans=20,
            seed=42,
            include_fair_lending=False,
            input_file=str(FIXTURE_CSV),
        )
        return run_pipeline(cfg), out

    def test_pipeline_produces_8_files(self, pipeline_result) -> None:
        result, out = pipeline_result
        assert len(result.files) == 8

    def test_ecl_non_negative(self, pipeline_result) -> None:
        result, _ = pipeline_result
        assert result.ecl_total >= 0.0

    def test_all_output_files_exist(self, pipeline_result) -> None:
        result, out = pipeline_result
        for fname in result.files:
            assert (out / fname).exists(), f"Missing: {fname}"
