"""Tests for orxaq.credit.filing — end-to-end filing generator."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from orxaq.credit.filing import (
    FILING_OUTPUT_FILES,
    FilingConfig,
    FilingGenerator,
    FilingResult,
)

# ── Fixtures ──────────────────────────────────────────────────


@pytest.fixture(scope="module")
def filing_result(tmp_path_factory) -> FilingResult:
    """Run FilingGenerator once and return result (module-scoped for speed)."""
    out = tmp_path_factory.mktemp("filing")
    cfg = FilingConfig(
        institution_id="TEST001",
        period="2026Q1",
        output_dir=str(out),
        seed=42,
        n_samples=100,
    )
    gen = FilingGenerator(cfg)
    return gen.generate()


# ── FilingConfig ──────────────────────────────────────────────


class TestFilingConfig:
    def test_defaults(self) -> None:
        cfg = FilingConfig()
        assert cfg.institution_id == "BANK001"
        assert cfg.period == "2026Q1"
        assert cfg.seed == 42
        assert cfg.n_samples == 500

    def test_custom_fields(self) -> None:
        cfg = FilingConfig(institution_id="MYBANK", period="2025Q4", seed=7)
        assert cfg.institution_id == "MYBANK"
        assert cfg.period == "2025Q4"
        assert cfg.seed == 7

    def test_horizon_quarters_default(self) -> None:
        assert FilingConfig().horizon_quarters == 9


# ── FilingGenerator.generate() ───────────────────────────────


class TestFilingGeneratorResult:
    def test_returns_filing_result(self, filing_result: FilingResult) -> None:
        assert isinstance(filing_result, FilingResult)

    def test_output_dir_created(self, filing_result: FilingResult) -> None:
        assert Path(filing_result.output_dir).exists()

    def test_duration_positive(self, filing_result: FilingResult) -> None:
        assert filing_result.duration_seconds > 0

    def test_audit_entries_positive(self, filing_result: FilingResult) -> None:
        assert filing_result.audit_entries_count > 0

    def test_period_in_result(self, filing_result: FilingResult) -> None:
        assert filing_result.period == "2026Q1"

    def test_institution_id_in_result(self, filing_result: FilingResult) -> None:
        assert filing_result.institution_id == "TEST001"


# ── Output file existence ─────────────────────────────────────


class TestFilingOutputFiles:
    def test_cecl_disclosure_exists(self, filing_result: FilingResult) -> None:
        assert Path(filing_result.output_dir, "cecl_disclosure.json").exists()

    def test_ccar_export_exists(self, filing_result: FilingResult) -> None:
        assert Path(filing_result.output_dir, "ccar_export.json").exists()

    def test_schedule_h1_csv_exists(self, filing_result: FilingResult) -> None:
        assert Path(filing_result.output_dir, "schedule_h1.csv").exists()

    def test_scenario_comparison_csv_exists(self, filing_result: FilingResult) -> None:
        assert Path(filing_result.output_dir, "scenario_comparison.csv").exists()

    def test_html_report_exists(self, filing_result: FilingResult) -> None:
        assert Path(filing_result.output_dir, "regulatory_report.html").exists()

    def test_markdown_report_exists(self, filing_result: FilingResult) -> None:
        assert Path(filing_result.output_dir, "regulatory_report.md").exists()

    def test_audit_trail_exists(self, filing_result: FilingResult) -> None:
        assert Path(filing_result.output_dir, "audit_trail.json").exists()

    def test_manifest_exists(self, filing_result: FilingResult) -> None:
        assert Path(filing_result.output_dir, "manifest.json").exists()


# ── Output file content ───────────────────────────────────────


class TestFilingOutputContent:
    def test_cecl_disclosure_is_valid_json(self, filing_result: FilingResult) -> None:
        data = json.loads(Path(filing_result.output_dir, "cecl_disclosure.json").read_text())
        assert isinstance(data, dict)

    def test_cecl_disclosure_has_framework(self, filing_result: FilingResult) -> None:
        data = json.loads(Path(filing_result.output_dir, "cecl_disclosure.json").read_text())
        assert "framework" in data

    def test_ccar_export_is_valid_json(self, filing_result: FilingResult) -> None:
        data = json.loads(Path(filing_result.output_dir, "ccar_export.json").read_text())
        assert isinstance(data, dict)

    def test_ccar_export_has_framework(self, filing_result: FilingResult) -> None:
        data = json.loads(Path(filing_result.output_dir, "ccar_export.json").read_text())
        assert "framework" in data

    def test_html_report_starts_with_doctype(self, filing_result: FilingResult) -> None:
        html = Path(filing_result.output_dir, "regulatory_report.html").read_text()
        assert "DOCTYPE" in html or len(html) > 0

    def test_markdown_report_nonempty(self, filing_result: FilingResult) -> None:
        md = Path(filing_result.output_dir, "regulatory_report.md").read_text()
        assert len(md) > 0

    def test_audit_trail_is_list(self, filing_result: FilingResult) -> None:
        data = json.loads(Path(filing_result.output_dir, "audit_trail.json").read_text())
        assert isinstance(data, list)
        assert len(data) > 0

    def test_audit_entries_have_action_and_timestamp(self, filing_result: FilingResult) -> None:
        data = json.loads(Path(filing_result.output_dir, "audit_trail.json").read_text())
        for entry in data:
            assert "action" in entry
            assert "timestamp" in entry

    def test_manifest_has_files_and_generated_at(self, filing_result: FilingResult) -> None:
        data = json.loads(Path(filing_result.output_dir, "manifest.json").read_text())
        assert "files" in data
        assert "generated_at" in data

    def test_manifest_checksums_are_sha256(self, filing_result: FilingResult) -> None:
        data = json.loads(Path(filing_result.output_dir, "manifest.json").read_text())
        for _fname, meta in data["files"].items():
            sha = meta["sha256"]
            assert len(sha) == 64
            assert all(c in "0123456789abcdef" for c in sha)

    def test_manifest_checksums_match_files(self, filing_result: FilingResult) -> None:
        import hashlib

        data = json.loads(Path(filing_result.output_dir, "manifest.json").read_text())
        for fname, meta in data["files"].items():
            fpath = Path(filing_result.output_dir, fname)
            if fpath.exists():
                actual = hashlib.sha256(fpath.read_bytes()).hexdigest()
                assert actual == meta["sha256"], f"Checksum mismatch for {fname}"


# ── Determinism ───────────────────────────────────────────────


class TestFilingDeterminism:
    def test_different_institution_ids_differ(self, tmp_path: Path) -> None:
        cfg_a = FilingConfig(
            institution_id="BANK_A",
            period="2026Q1",
            output_dir=str(tmp_path / "a"),
            seed=42,
            n_samples=50,
        )
        cfg_b = FilingConfig(
            institution_id="BANK_B",
            period="2026Q1",
            output_dir=str(tmp_path / "b"),
            seed=42,
            n_samples=50,
        )
        result_a = FilingGenerator().generate(cfg_a)
        result_b = FilingGenerator().generate(cfg_b)
        data_a = json.loads(Path(result_a.output_dir, "cecl_disclosure.json").read_text())
        data_b = json.loads(Path(result_b.output_dir, "cecl_disclosure.json").read_text())
        assert data_a.get("institution_id") == "BANK_A"
        assert data_b.get("institution_id") == "BANK_B"

    def test_institution_id_appears_in_manifest(self, filing_result: FilingResult) -> None:
        data = json.loads(Path(filing_result.output_dir, "manifest.json").read_text())
        assert data["institution_id"] == "TEST001"


# ── FILING_OUTPUT_FILES constant ─────────────────────────────


class TestFilingOutputFilesConstant:
    def test_constant_has_eight_entries(self) -> None:
        assert len(FILING_OUTPUT_FILES) == 8

    def test_all_canonical_files_present(self, filing_result: FilingResult) -> None:
        for fname in FILING_OUTPUT_FILES:
            p = Path(filing_result.output_dir, fname)
            assert p.exists(), f"Expected {fname} to exist in {filing_result.output_dir}"


# ── ScenarioComparisonContent ─────────────────────────────────


class TestScenarioComparisonContent:
    """Verify scenario_comparison.csv contains real delta analytics from ScenarioComparator."""

    def _csv_rows(self, filing_result: FilingResult):
        import csv

        p = Path(filing_result.output_dir, "scenario_comparison.csv")
        with p.open(newline="", encoding="utf-8") as fh:
            return list(csv.DictReader(fh))

    def test_csv_non_empty(self, filing_result: FilingResult) -> None:
        rows = self._csv_rows(filing_result)
        assert len(rows) > 0

    def test_has_scenario_column(self, filing_result: FilingResult) -> None:
        rows = self._csv_rows(filing_result)
        assert "scenario" in rows[0]

    def test_has_quarter_column(self, filing_result: FilingResult) -> None:
        rows = self._csv_rows(filing_result)
        assert "quarter" in rows[0]

    def test_rows_for_all_three_scenarios(self, filing_result: FilingResult) -> None:
        rows = self._csv_rows(filing_result)
        scenarios = {r["scenario"] for r in rows}
        assert {"baseline", "adverse", "severely_adverse"} == scenarios

    def test_is_peak_quarter_true_exists(self, filing_result: FilingResult) -> None:
        rows = self._csv_rows(filing_result)
        assert any(r.get("is_peak_quarter") in ("True", "true", "1", True) for r in rows)

    def test_ending_cet1_numeric(self, filing_result: FilingResult) -> None:
        rows = self._csv_rows(filing_result)
        for r in rows:
            if "ending_cet1" in r:
                assert float(r["ending_cet1"]) >= 0.0
                break


# ── Targeted coverage for private helpers ────────────────────


class TestWriteCsvEmpty:
    """Cover lines 318-319: _write_csv with empty rows."""

    def test_empty_rows(self, tmp_path):
        from orxaq.credit.filing import _write_csv

        p = tmp_path / "empty.csv"
        _write_csv(p, [])
        assert p.read_text() == ""


class TestBuildScenarioComparisonLegacy:
    """Cover lines 334-348: _build_scenario_comparison legacy fallback."""

    def test_with_valid_report(self):
        from orxaq.credit.filing import _build_scenario_comparison

        class FakeResult:
            ecl = 0.05
            pd_mean = 0.03

        class FakeReport:
            scenario_results = {
                "severely_adverse": type("R", (), {"quarterly_results": [FakeResult()]})(),
            }

        rows = _build_scenario_comparison(FakeReport())
        assert len(rows) == 1
        assert rows[0]["scenario"] == "severely_adverse"
        assert rows[0]["quarter"] == 1

    def test_attribute_error_fallback(self):
        from orxaq.credit.filing import _build_scenario_comparison

        rows = _build_scenario_comparison(object())
        assert len(rows) == 1
        assert rows[0]["scenario"] == "severely_adverse"


class TestBuildScenarioComparisonRichFallback:
    """Cover lines 381-382: _build_scenario_comparison_rich exception fallback."""

    def test_fallback_on_single_report(self):
        from orxaq.credit.filing import _build_scenario_comparison_rich

        # Single report triggers ValueError in ScenarioComparator (needs >=2)
        # which falls back to _build_scenario_comparison
        rows = _build_scenario_comparison_rich({"baseline": object()})
        assert isinstance(rows, list)
        # Fallback goes to _build_scenario_comparison which catches AttributeError
        assert len(rows) >= 1


class TestWriteJsonHelper:
    """Cover line 313: _write_json writes valid JSON."""

    def test_write_json(self, tmp_path):
        from orxaq.credit.filing import _write_json

        p = tmp_path / "test.json"
        _write_json(p, {"key": "value"})
        assert json.loads(p.read_text()) == {"key": "value"}
