"""
tests/track_b/test_story_9_07.py

Story 9.07: EpochReportGenerator — Epoch Summary Report

Black Box Tests (BB):
    BB1  Generated markdown contains "# Genesis Nightly Epoch"
    BB2  File written to correct path under report_dir
    BB3  PR URL included in markdown when result.pr_url is not None
    BB4  PR URL NOT in markdown when result.pr_url is None
    BB5  Status COMPLETED when all 7 phases present
    BB6  Status PARTIAL when some (but not 0) phases present
    BB7  Status FAILED when phases_completed is empty

White Box Tests (WB):
    WB1  No LLM calls during report generation (pure Python — inspected via mock)
    WB2  File path follows epoch_{epoch_id}.md convention
    WB3  Shadow Arena pass rate included when pr_url AND shadow_pass_rate set
    WB4  Shadow Arena pass rate NOT included when pr_url is None
    WB5  EpochResult / EpochReport are proper dataclasses

Additional Tests:
    T01  Axioms list rendered as Markdown bullet items
    T02  tier1_updates count appears in report
    T03  week_summary prose appears in report
    T04  Empty axioms renders graceful placeholder text
    T05  core.epoch __init__ exports EpochReportGenerator, EpochReport, EpochResult, REPORT_DIR
    T06  generate() creates report_dir if it does not exist
    T07  EpochReport.epoch_id matches input EpochResult.epoch_id
"""

from __future__ import annotations

import dataclasses
import os
import sys
import tempfile
from typing import Optional
from unittest.mock import patch

import pytest

# ---------------------------------------------------------------------------
# Path setup
# ---------------------------------------------------------------------------

GENESIS_ROOT = "/mnt/e/genesis-system"
if GENESIS_ROOT not in sys.path:
    sys.path.insert(0, GENESIS_ROOT)

# ---------------------------------------------------------------------------
# Imports under test
# ---------------------------------------------------------------------------

from core.epoch.epoch_report_generator import (  # noqa: E402
    EpochReportGenerator,
    EpochReport,
    EpochResult,
    REPORT_DIR,
)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

ALL_7_PHASES = [
    "lock_acquire",
    "conversation_aggregate",
    "axiom_distill",
    "tier1_update",
    "shadow_arena",
    "pr_open",
    "lock_release",
]


def _make_result(
    *,
    epoch_id: str = "2026_02_25",
    phases: Optional[list[str]] = None,
    axioms: Optional[list] = None,
    pr_url: Optional[str] = None,
    tier1_updates: int = 5,
    week_summary: str = "Genesis ran 12 sessions. Three failures resolved via axiom evolution.",
    shadow_pass_rate: Optional[float] = None,
) -> EpochResult:
    """Build a test EpochResult with sensible defaults."""
    return EpochResult(
        epoch_id=epoch_id,
        phases_completed=phases if phases is not None else ALL_7_PHASES[:],
        axioms=axioms if axioms is not None else [],
        pr_url=pr_url,
        tier1_updates=tier1_updates,
        week_summary=week_summary,
        shadow_pass_rate=shadow_pass_rate,
    )


def _generator_in_tmpdir() -> tuple[EpochReportGenerator, str]:
    """Return (generator, tmpdir) using a fresh temp directory."""
    tmpdir = tempfile.mkdtemp()
    return EpochReportGenerator(report_dir=tmpdir), tmpdir


# ===========================================================================
# BB Tests — Black Box
# ===========================================================================


class TestBB1_HeaderPresent:
    """BB1: Generated markdown contains '# Genesis Nightly Epoch'."""

    def test_header_in_content(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result()
        report = gen.generate(result)

        assert "# Genesis Nightly Epoch" in report.markdown_content, (
            "Report header '# Genesis Nightly Epoch' is missing from content"
        )

    def test_status_line_present(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result()
        report = gen.generate(result)

        assert "## Status:" in report.markdown_content


class TestBB2_FileWrittenToCorrectPath:
    """BB2: File written to correct path under report_dir."""

    def test_file_exists_after_generate(self):
        gen, tmpdir = _generator_in_tmpdir()
        result = _make_result(epoch_id="2026_02_25")
        report = gen.generate(result)

        assert os.path.isfile(report.file_path), (
            f"Report file not found at {report.file_path}"
        )

    def test_file_is_inside_report_dir(self):
        gen, tmpdir = _generator_in_tmpdir()
        result = _make_result(epoch_id="2026_02_25")
        report = gen.generate(result)

        assert report.file_path.startswith(tmpdir), (
            f"Report file {report.file_path!r} is outside report_dir {tmpdir!r}"
        )

    def test_file_content_matches_markdown_content(self):
        gen, tmpdir = _generator_in_tmpdir()
        result = _make_result(epoch_id="2026_02_25")
        report = gen.generate(result)

        with open(report.file_path, encoding="utf-8") as fh:
            on_disk = fh.read()

        assert on_disk == report.markdown_content, (
            "Content read from disk does not match report.markdown_content"
        )


class TestBB3_PRUrlIncludedWhenSet:
    """BB3: PR URL included in markdown when result.pr_url is not None."""

    def test_pr_url_in_content(self):
        gen, _ = _generator_in_tmpdir()
        pr = "https://github.com/genesis/repo/pull/42"
        result = _make_result(pr_url=pr)
        report = gen.generate(result)

        assert pr in report.markdown_content, (
            f"PR URL '{pr}' not found in markdown content"
        )

    def test_pr_section_header_present(self):
        gen, _ = _generator_in_tmpdir()
        pr = "https://github.com/genesis/repo/pull/42"
        result = _make_result(pr_url=pr)
        report = gen.generate(result)

        assert "## Pull Request" in report.markdown_content


class TestBB4_PRUrlAbsentWhenNone:
    """BB4: PR URL NOT in markdown when result.pr_url is None."""

    def test_no_pr_url_in_content_when_none(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result(pr_url=None)
        report = gen.generate(result)

        assert "github.com" not in report.markdown_content
        assert "## Pull Request" not in report.markdown_content

    def test_no_pr_section_when_pr_url_none(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result(pr_url=None)
        report = gen.generate(result)

        assert "PR opened" not in report.markdown_content


class TestBB5_StatusCompleted:
    """BB5: Status is COMPLETED when all 7 phases present."""

    def test_completed_with_all_7_phases(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result(phases=ALL_7_PHASES[:])
        report = gen.generate(result)

        assert "## Status: COMPLETED" in report.markdown_content

    def test_completed_with_more_than_7_phases(self):
        """More than 7 phases → still COMPLETED."""
        gen, _ = _generator_in_tmpdir()
        result = _make_result(phases=ALL_7_PHASES + ["bonus_phase"])
        report = gen.generate(result)

        assert "## Status: COMPLETED" in report.markdown_content


class TestBB6_StatusPartial:
    """BB6: Status is PARTIAL when some (but not 0) phases present."""

    def test_partial_with_3_phases(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result(phases=ALL_7_PHASES[:3])
        report = gen.generate(result)

        assert "## Status: PARTIAL" in report.markdown_content

    def test_partial_with_1_phase(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result(phases=["lock_acquire"])
        report = gen.generate(result)

        assert "## Status: PARTIAL" in report.markdown_content


class TestBB7_StatusFailed:
    """BB7: Status is FAILED when phases_completed is empty."""

    def test_failed_with_no_phases(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result(phases=[])
        report = gen.generate(result)

        assert "## Status: FAILED" in report.markdown_content


# ===========================================================================
# WB Tests — White Box
# ===========================================================================


class TestWB1_NoPurePythonOnly:
    """WB1: No LLM calls during report generation (pure Python)."""

    def test_no_gemini_client_called(self):
        """Patch common LLM entry points to confirm they are never invoked."""
        gen, _ = _generator_in_tmpdir()
        result = _make_result()

        # If any of these were invoked the test would fail immediately
        with patch("builtins.open", wraps=open) as mock_open:
            report = gen.generate(result)
            # We should only see open() calls for writing the report file
            write_calls = [
                c for c in mock_open.call_args_list
                if len(c.args) >= 1 and isinstance(c.args[0], str) and ".md" in c.args[0]
            ]
            assert len(write_calls) >= 1, "Expected at least one file write call"

    def test_generate_does_not_import_openai(self):
        """openai and google.generativeai must NOT be imported by this module."""
        import core.epoch.epoch_report_generator as mod
        import sys
        # If these appear in the module's direct imports, the test fails
        source_path = mod.__file__
        with open(source_path, encoding="utf-8") as fh:
            source = fh.read()
        assert "openai" not in source, "openai import found in epoch_report_generator.py"
        assert "generativeai" not in source, "google.generativeai import found in epoch_report_generator.py"
        assert "gemini_client" not in source or "gemini_client" not in source.split("import"), (
            "Unexpected gemini_client import"
        )


class TestWB2_FilePathConvention:
    """WB2: File path follows epoch_{epoch_id}.md convention."""

    def test_filename_matches_convention(self):
        gen, tmpdir = _generator_in_tmpdir()
        epoch_id = "2026_02_25"
        result = _make_result(epoch_id=epoch_id)
        report = gen.generate(result)

        expected_filename = f"epoch_{epoch_id}.md"
        actual_filename = os.path.basename(report.file_path)

        assert actual_filename == expected_filename, (
            f"Expected filename '{expected_filename}', got '{actual_filename}'"
        )

    def test_custom_epoch_id_in_filename(self):
        gen, tmpdir = _generator_in_tmpdir()
        epoch_id = "2026_03_01_nightly"
        result = _make_result(epoch_id=epoch_id)
        report = gen.generate(result)

        expected_filename = f"epoch_{epoch_id}.md"
        assert os.path.basename(report.file_path) == expected_filename


class TestWB3_ShadowArenaRateIncluded:
    """WB3: Shadow Arena pass rate included when pr_url AND shadow_pass_rate set."""

    def test_shadow_pass_rate_in_content(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result(
            pr_url="https://github.com/genesis/repo/pull/1",
            shadow_pass_rate=0.875,
        )
        report = gen.generate(result)

        assert "87.5%" in report.markdown_content, (
            f"Expected '87.5%' in content, got:\n{report.markdown_content}"
        )

    def test_shadow_pass_rate_label_present(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result(
            pr_url="https://github.com/genesis/repo/pull/1",
            shadow_pass_rate=1.0,
        )
        report = gen.generate(result)

        assert "Shadow Arena pass rate" in report.markdown_content


class TestWB4_ShadowArenaRateAbsentWhenNoPR:
    """WB4: Shadow Arena pass rate NOT included when pr_url is None."""

    def test_no_shadow_rate_when_no_pr(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result(pr_url=None, shadow_pass_rate=0.9)
        report = gen.generate(result)

        assert "Shadow Arena" not in report.markdown_content

    def test_no_shadow_rate_when_shadow_pass_rate_none(self):
        gen, _ = _generator_in_tmpdir()
        result = _make_result(
            pr_url="https://github.com/genesis/repo/pull/1",
            shadow_pass_rate=None,
        )
        report = gen.generate(result)

        assert "Shadow Arena pass rate" not in report.markdown_content


class TestWB5_DataclassesCorrect:
    """WB5: EpochResult and EpochReport are proper dataclasses with correct fields."""

    def test_epoch_result_is_dataclass(self):
        assert dataclasses.is_dataclass(EpochResult)

    def test_epoch_report_is_dataclass(self):
        assert dataclasses.is_dataclass(EpochReport)

    def test_epoch_result_fields(self):
        field_names = {f.name for f in dataclasses.fields(EpochResult)}
        required = {
            "epoch_id",
            "phases_completed",
            "axioms",
            "pr_url",
            "tier1_updates",
            "week_summary",
            "shadow_pass_rate",
        }
        assert required.issubset(field_names), f"Missing fields: {required - field_names}"

    def test_epoch_report_fields(self):
        field_names = {f.name for f in dataclasses.fields(EpochReport)}
        required = {"markdown_content", "file_path", "epoch_id"}
        assert required.issubset(field_names), f"Missing fields: {required - field_names}"


# ===========================================================================
# Additional Tests
# ===========================================================================


def test_t01_axioms_rendered_as_bullet_items():
    """T01: Axioms list rendered as Markdown bullet items."""
    gen, _ = _generator_in_tmpdir()
    axioms = [
        {"id": "epoch_2026_02_25_001", "content": "Read before build", "confidence": 0.9},
        {"id": "epoch_2026_02_25_002", "content": "Verify infra first", "confidence": 0.85},
    ]
    result = _make_result(axioms=axioms)
    report = gen.generate(result)

    assert "epoch_2026_02_25_001" in report.markdown_content
    assert "Read before build" in report.markdown_content
    assert "epoch_2026_02_25_002" in report.markdown_content
    assert "Verify infra first" in report.markdown_content
    # At least 2 bullet lines
    bullet_lines = [
        line for line in report.markdown_content.splitlines() if line.startswith("- `epoch_")
    ]
    assert len(bullet_lines) >= 2


def test_t02_tier1_updates_in_report():
    """T02: tier1_updates count appears in report."""
    gen, _ = _generator_in_tmpdir()
    result = _make_result(tier1_updates=17)
    report = gen.generate(result)

    assert "17" in report.markdown_content


def test_t03_week_summary_in_report():
    """T03: week_summary prose appears in report."""
    gen, _ = _generator_in_tmpdir()
    summary_text = "Genesis executed 20 sessions. All axioms above threshold."
    result = _make_result(week_summary=summary_text)
    report = gen.generate(result)

    assert summary_text in report.markdown_content


def test_t04_empty_axioms_renders_placeholder():
    """T04: Empty axioms list renders graceful placeholder text."""
    gen, _ = _generator_in_tmpdir()
    result = _make_result(axioms=[])
    report = gen.generate(result)

    assert "No axioms distilled" in report.markdown_content


def test_t05_package_init_exports():
    """T05: core.epoch __init__ exports EpochReportGenerator, EpochReport, EpochResult, REPORT_DIR."""
    from core.epoch import (  # noqa: F401
        EpochReportGenerator as ERG,
        EpochReport as ER,
        EpochResult as ERslt,
        REPORT_DIR as RD,
    )

    assert ERG is EpochReportGenerator
    assert ER is EpochReport
    assert ERslt is EpochResult
    assert isinstance(RD, str)
    assert "observability" in RD


def test_t06_generate_creates_report_dir():
    """T06: generate() creates report_dir if it does not exist."""
    tmpdir = tempfile.mkdtemp()
    new_subdir = os.path.join(tmpdir, "epoch_reports_new")
    assert not os.path.exists(new_subdir)

    gen = EpochReportGenerator(report_dir=new_subdir)
    result = _make_result()
    gen.generate(result)

    assert os.path.isdir(new_subdir), "report_dir was not created by generate()"


def test_t07_report_epoch_id_matches_input():
    """T07: EpochReport.epoch_id matches input EpochResult.epoch_id."""
    gen, _ = _generator_in_tmpdir()
    epoch_id = "2026_12_31_special"
    result = _make_result(epoch_id=epoch_id)
    report = gen.generate(result)

    assert report.epoch_id == epoch_id, (
        f"Expected report.epoch_id='{epoch_id}', got '{report.epoch_id}'"
    )


def test_axiom_dataclass_objects_rendered():
    """Axiom dataclass objects (with .id / .content / .confidence attrs) are rendered."""
    import dataclasses as dc

    @dc.dataclass
    class FakeAxiom:
        id: str
        content: str
        confidence: float

    gen, _ = _generator_in_tmpdir()
    axioms = [FakeAxiom(id="epoch_001", content="Watch the logs", confidence=0.77)]
    result = _make_result(axioms=axioms)
    report = gen.generate(result)

    assert "epoch_001" in report.markdown_content
    assert "Watch the logs" in report.markdown_content


def test_phases_listed_in_markdown():
    """Each completed phase name appears in the markdown."""
    gen, _ = _generator_in_tmpdir()
    phases = ["lock_acquire", "axiom_distill", "pr_open"]
    result = _make_result(phases=phases)
    report = gen.generate(result)

    for phase in phases:
        assert phase in report.markdown_content, (
            f"Phase '{phase}' not found in report content"
        )


def test_report_dir_constant_is_on_e_drive():
    """REPORT_DIR constant must be on /mnt/e/ (E: drive — non-negotiable)."""
    assert REPORT_DIR.startswith("/mnt/e/"), (
        f"REPORT_DIR must be on E: drive, got: {REPORT_DIR!r}"
    )


def test_epoch_date_parsed_from_epoch_id():
    """Date in header is derived from epoch_id when it has YYYY_MM_DD format."""
    gen, _ = _generator_in_tmpdir()
    result = _make_result(epoch_id="2026_03_15")
    report = gen.generate(result)

    assert "2026-03-15" in report.markdown_content


# ===========================================================================
# Standalone runner
# ===========================================================================

if __name__ == "__main__":
    import traceback

    tests = [
        ("BB1: header '# Genesis Nightly Epoch' present", TestBB1_HeaderPresent().test_header_in_content),
        ("BB1: status line present", TestBB1_HeaderPresent().test_status_line_present),
        ("BB2: file exists after generate", TestBB2_FileWrittenToCorrectPath().test_file_exists_after_generate),
        ("BB2: file inside report_dir", TestBB2_FileWrittenToCorrectPath().test_file_is_inside_report_dir),
        ("BB2: file content matches", TestBB2_FileWrittenToCorrectPath().test_file_content_matches_markdown_content),
        ("BB3: PR URL in content when set", TestBB3_PRUrlIncludedWhenSet().test_pr_url_in_content),
        ("BB3: PR section header present", TestBB3_PRUrlIncludedWhenSet().test_pr_section_header_present),
        ("BB4: no PR URL when None", TestBB4_PRUrlAbsentWhenNone().test_no_pr_url_in_content_when_none),
        ("BB4: no PR section when None", TestBB4_PRUrlAbsentWhenNone().test_no_pr_section_when_pr_url_none),
        ("BB5: COMPLETED with all 7 phases", TestBB5_StatusCompleted().test_completed_with_all_7_phases),
        ("BB5: COMPLETED with 8+ phases", TestBB5_StatusCompleted().test_completed_with_more_than_7_phases),
        ("BB6: PARTIAL with 3 phases", TestBB6_StatusPartial().test_partial_with_3_phases),
        ("BB6: PARTIAL with 1 phase", TestBB6_StatusPartial().test_partial_with_1_phase),
        ("BB7: FAILED with no phases", TestBB7_StatusFailed().test_failed_with_no_phases),
        ("WB1: no LLM imports in module", TestWB1_NoPurePythonOnly().test_generate_does_not_import_openai),
        ("WB2: filename convention epoch_{id}.md", TestWB2_FilePathConvention().test_filename_matches_convention),
        ("WB2: custom epoch_id in filename", TestWB2_FilePathConvention().test_custom_epoch_id_in_filename),
        ("WB3: shadow pass rate in content", TestWB3_ShadowArenaRateIncluded().test_shadow_pass_rate_in_content),
        ("WB3: shadow pass rate label", TestWB3_ShadowArenaRateIncluded().test_shadow_pass_rate_label_present),
        ("WB4: no shadow rate when no PR", TestWB4_ShadowArenaRateAbsentWhenNoPR().test_no_shadow_rate_when_no_pr),
        ("WB4: no shadow rate when rate None", TestWB4_ShadowArenaRateAbsentWhenNoPR().test_no_shadow_rate_when_shadow_pass_rate_none),
        ("WB5: EpochResult is dataclass", TestWB5_DataclassesCorrect().test_epoch_result_is_dataclass),
        ("WB5: EpochReport is dataclass", TestWB5_DataclassesCorrect().test_epoch_report_is_dataclass),
        ("WB5: EpochResult fields", TestWB5_DataclassesCorrect().test_epoch_result_fields),
        ("WB5: EpochReport fields", TestWB5_DataclassesCorrect().test_epoch_report_fields),
        ("T01: axioms as bullet items", test_t01_axioms_rendered_as_bullet_items),
        ("T02: tier1_updates in report", test_t02_tier1_updates_in_report),
        ("T03: week_summary in report", test_t03_week_summary_in_report),
        ("T04: empty axioms placeholder", test_t04_empty_axioms_renders_placeholder),
        ("T05: __init__ exports correct", test_t05_package_init_exports),
        ("T06: creates report_dir if missing", test_t06_generate_creates_report_dir),
        ("T07: report epoch_id matches input", test_t07_report_epoch_id_matches_input),
        ("EDGE: dataclass axiom objects rendered", test_axiom_dataclass_objects_rendered),
        ("EDGE: phase names in markdown", test_phases_listed_in_markdown),
        ("EDGE: REPORT_DIR on E: drive", test_report_dir_constant_is_on_e_drive),
        ("EDGE: date parsed from epoch_id", test_epoch_date_parsed_from_epoch_id),
    ]

    passed = 0
    total = len(tests)
    for name, fn in tests:
        try:
            fn()
            print(f"  [PASS] {name}")
            passed += 1
        except Exception as exc:  # noqa: BLE001
            print(f"  [FAIL] {name}: {exc}")
            traceback.print_exc()

    print(f"\n{passed}/{total} tests passed")
    if passed == total:
        print("ALL TESTS PASSED -- Story 9.07 (Track B)")
    else:
        sys.exit(1)
