"""E2E test for Scenario A: Formal Theory -- Quantum Anomaly in 2+1D Gauge Theory.

Simulates the full multi-turn conversation from e2e_benchmark_scenarios.md:
  L1 -> L3(ideate->plan->derive->integrate->distill) -> L4(pass) -> L2(promotion)

Covers: source registration, convention snapshot, activity traversal,
candidate submission, validation contract, L4 review, L2 promotion,
and all verification checkpoints.
"""

from __future__ import annotations

import tempfile
import unittest
from pathlib import Path

from brain import mcp_server


TOPIC_SLUG = "chern-simons-anomaly"
TOPIC_TITLE = "Parity Anomaly in 2+1D U(N) Chern-Simons Theory"
LANE = "formal_theory"


def _bootstrap_and_register_sources(tmp: str) -> Path:
    """Turns 1-3: bootstrap, register 3 sources, fill L1 artifacts."""
    repo_root = Path(tmp)
    (repo_root / "topics").mkdir(exist_ok=True)

    # Turn 1: bootstrap
    mcp_server.aitp_bootstrap_topic(
        str(repo_root), TOPIC_SLUG,
        TOPIC_TITLE,
        "Does a 2+1D U(N) Chern-Simons gauge theory coupled to massless "
        "Dirac fermions develop a parity anomaly, producing a level shift "
        "k -> k +/- N/2?",
    )

    tr = repo_root / "topics" / TOPIC_SLUG

    # Set lane
    state_fm, state_body = mcp_server._parse_md(tr / "state.md")
    state_fm["lane"] = LANE
    mcp_server._write_md(tr / "state.md", state_fm, state_body)

    # Turn 2: register 3 sources
    mcp_server.aitp_register_source(
        tmp, TOPIC_SLUG, "witten-cs-2018",
        source_type="paper",
        title="arXiv:1805.06306 -- Witten on Chern-Simons Theory",
    )
    mcp_server.aitp_register_source(
        tmp, TOPIC_SLUG, "redlich-parity-2016",
        source_type="paper",
        title="arXiv:1609.04370 -- Redlich on Parity Anomalies",
    )
    mcp_server.aitp_register_source(
        tmp, TOPIC_SLUG, "weinberg-qft-v2",
        source_type="book",
        title="Weinberg QFT Vol II, Chapter 22",
    )

    # Fill L0 source_registry to make L0 gate ready
    mcp_server._write_md(
        tr / "L0" / "source_registry.md",
        {"artifact_kind": "l0_source_registry", "stage": "L0",
         "source_count": 3, "search_status": "complete"},
        "# Source Registry\n\n## Search Methodology\narxiv + textbook\n\n"
        "## Source Inventory\nwitten-cs-2018, redlich-parity-2016, weinberg-qft-v2\n\n"
        "## Coverage Assessment\nAdequate for one-loop CS computation\n\n"
        "## Overall Verdict\nThis assessment confirms that the registered sources are sufficient for the bounded research question. Coverage spans the core derivation path and primary validation checks. No blocking gaps were identified. Source quality is adequate for current research intensity level.\n\n"
        "## Gaps And Next Sources\nNone\n\n## Prior L2 Knowledge\nNo prior L2 knowledge for this test bootstrap topic. Unit test fixture for gate verification.\n",
    )
    mcp_server.aitp_advance_to_l1(tmp, TOPIC_SLUG)

    # Turn 3: fill all L1 artifacts
    mcp_server._write_md(
        tr / "L1" / "question_contract.md",
        {"artifact_kind": "l1_question_contract", "stage": "L1",
         "bounded_question": "Compute the one-loop effective action for N massless "
                            "Dirac fermions in the fundamental of U(N) CS theory "
                            "at level k and determine the level shift.",
         "scope_boundaries": "One-loop, massless fermions, fundamental rep, 2+1D. "
                            "Does NOT claim all-loop order or other representations.",
         "target_quantities": "Level shift Delta_k = N*sgn(m)/2",
         "competing_hypotheses": "Shift vanishes in SUSY extension; possible scheme dependence."},
        "# Question Contract\n\n"
        "## Bounded Question\n"
        "Does a 2+1D U(N) Chern-Simons gauge theory coupled to N massless Dirac "
        "fermions in the fundamental produce a one-loop level shift?\n\n"
        "## Competing Hypotheses\n"
        "Shift vanishes in SUSY extension; possible scheme dependence.\n\n"
        "## Scope Boundaries\n"
        "One-loop only, massless fermions, fundamental representation, 2+1D. "
        "Does NOT claim all-loop order or other representations.\n\n"
        "## Target Quantities Or Claims\n"
        "Level shift Delta_k = N*sgn(m)/2.\n\n"
        "## Non-Success Conditions\n"
        "No claim about all-loop order or other representations.\n\n"
        "## Uncertainty Markers\n"
        "Regularization scheme dependence to be checked.\n\n## L2 Cross-Reference\nNo prior L2 knowledge for this test topic. Unit test fixture.\n",
    )
    mcp_server._write_md(
        tr / "L1" / "source_basis.md",
        {"artifact_kind": "l1_source_basis", "stage": "L1",
         "core_sources": "witten-cs-2018, redlich-parity-2016",
         "peripheral_sources": "weinberg-qft-v2"},
        "# Source Basis\n\n"
        "## Core Sources\n"
        "witten-cs-2018, redlich-parity-2016\n\n"
        "## Peripheral Sources\nweinberg-qft-v2\n\n"
        "## Source Roles\n"
        "witten-cs-2018 defines the CS framework; redlich-parity-2016 provides "
        "the parity anomaly baseline for N=1.\n\n"
        "## Reading Depth\nfull_read for core, chapter_read for Weinberg Ch22.\n\n"
        "## Why Each Source Matters\n"
        "witten-cs-2018 gives the general CS effective action; "
        "redlich-parity-2016 provides the known N=1 result to check against.\n",
    )
    mcp_server._write_md(
        tr / "L1" / "convention_snapshot.md",
        {"artifact_kind": "l1_convention_snapshot", "stage": "L1",
         "notation_choices": "CS normalization S_CS = k/(4pi) int tr(A dA + 2/3 A^3)",
         "unit_conventions": "Natural units hbar=c=1"},
        "# Convention Snapshot\n\n"
        "## Notation Choices\n"
        "S_CS = k/(4pi) int tr(A dA + 2/3 A^3)\n\n"
        "## Unit Conventions\nNatural units hbar=c=1.\n\n"
        "## Sign Conventions\nMostly-plus metric (+--).\n\n"
        "## Metric Or Coordinate Conventions\nMostly-plus (+--), Euclidean continuation for determinant.\n\n"
        "## Categorized Assumptions\nMathematical: U(N) gauge group, compact 3-manifold. "
        "Physical: massless limit, zero temperature.\n\n"
        "## Unresolved Tensions\nNone blocking.\n",
    )
    mcp_server._write_md(
        tr / "L1" / "derivation_anchor_map.md",
        {"artifact_kind": "l1_derivation_anchor_map", "stage": "L1",
         "starting_anchors": "Dirac operator in 2+1D, Pauli-Villars regulator",
         "anchor_count": 2},
        "# Derivation Anchor Map\n\n"
        "## Source Anchors\n"
        "Dirac operator in 2+1D, Pauli-Villars regulator.\n\n"
        "## Dependency Graph\n"
        "Dirac operator -> proper-time regulator -> zeta-function -> CS term.\n\n"
        "## Missing Steps\n"
        "Regularization of functional determinant.\n\n"
        "## Candidate Starting Points\n"
        "Dirac operator -> proper-time -> zeta-function -> extract CS term.\n",
    )
    mcp_server._write_md(
        tr / "L1" / "contradiction_register.md",
        {"artifact_kind": "l1_contradiction_register", "stage": "L1",
         "blocking_contradictions": "none"},
        "# Contradiction Register\n\n"
        "## Unresolved Source Conflicts\nNone.\n\n"
        "## Internal Inconsistencies\nNone detected.\n\n"
        "## Regime Mismatches\nNone blocking.\n\n"
        "## Notation Collisions\n"
        "CS level convention aligned with Witten's normalization.\n\n"
        "## Blocking Status\nnone\n",
    )

    # Write source_toc_map.md (required by L1 coverage gate)
    mcp_server._write_md(
        tr / "L1" / "source_toc_map.md",
        {"artifact_kind": "l1_source_toc_map", "stage": "L1",
         "sources_with_toc": "witten-cs-2018",
         "total_sections": 2, "coverage_status": "complete"},
        "# Source TOC Map\n\n## Per-Source TOC\n\n"
        "### witten-cs-2018 (TOC confidence: high)\n\n"
        "- [s1] Section 1 -- status: extracted  -> intake: L1/intake/witten-cs-2018/s1.md\n"
        "- [s2] Section 2 -- status: extracted  -> intake: L1/intake/witten-cs-2018/s2.md\n\n"
        "## Coverage Summary\n\n"
        "## Deferred Sections\n\n"
        "## Extraction Notes\n",
    )
    # Create intake notes for extracted sections (required by L1 gate)
    for sec_id in ["s1", "s2"]:
        intake_dir = tr / "L1" / "intake" / "witten-cs-2018"
        intake_dir.mkdir(parents=True, exist_ok=True)
        mcp_server._write_md(
            intake_dir / f"{sec_id}.md",
            {"artifact_kind": "l1_section_intake", "source_id": "witten-cs-2018",
             "section_id": sec_id, "section_title": f"Section {sec_id}",
             "extraction_status": "extracted", "completeness_confidence": "high",
             "updated_at": "2025-01-01T00:00:00Z"},
            f"# Section {sec_id}\n\n## Section Summary (skim)\nContent.\n\n"
            "## Key Concepts\nCS term.\n\n## Equations Found\nS = k/(4pi) int.\n\n"
            "## Physical Claims\nAnomaly cancellation condition.\n\n"
            "## Prerequisites\nQFT, differential forms.\n\n"
            "## Cross-References\nNone.\n\n## Completeness Self-Assessment\nConfidence: **high**\n",
        )

    # Verify L1 gate is ready
    brief = mcp_server.aitp_get_execution_brief(tmp, TOPIC_SLUG)
    assert brief["gate_status"] == "ready", f"L1 gate not ready: {brief}"

    return repo_root


def _advance_l3_all_activities(tmp: str, repo_root: Path) -> None:
    """Turns 4-6: advance through L3 activities with physics content."""
    tr = repo_root / "topics" / TOPIC_SLUG

    mcp_server.aitp_advance_to_l3(tmp, TOPIC_SLUG)

    # ideate
    mcp_server._write_md(
        tr / "L3" / "ideate" / "active_idea.md",
        {"artifact_kind": "l3_active_idea", "activity": "ideate",
         "idea_statement": "Compute the one-loop fermion determinant in 2+1D CS theory",
         "motivation": "The determinant generates an effective CS term, shifting the level"},
        "# Active Idea\n\n"
        "## Idea Statement\n"
        "Compute the one-loop fermion determinant in 2+1D CS theory to extract "
        "the induced CS level shift.\n\n"
        "## Motivation\n"
        "The parity anomaly manifests as an induced CS term from integrating out "
        "Dirac fermions, producing a level shift Delta_k = N*sgn(m)/2.\n",
    )
    mcp_server.aitp_switch_l3_activity(tmp, TOPIC_SLUG, "plan")

    # plan
    mcp_server._write_md(
        tr / "L3" / "plan" / "active_plan.md",
        {"artifact_kind": "l3_active_plan", "activity": "plan",
         "plan_statement": "Regularize the fermion determinant via Pauli-Villars",
         "derivation_route": "Dirac operator -> proper-time -> Pauli-Villars -> extract CS term"},
        "# Active Plan\n\n"
        "## Plan Statement\n"
        "Regularize the fermion determinant via Pauli-Villars regularization and "
        "extract the induced CS term.\n\n"
        "## Derivation Route\n"
        "1. Write Dirac operator in 2+1D coupled to background CS gauge field.\n"
        "2. Express fermion determinant via proper-time representation.\n"
        "3. Regularize using Pauli-Villars (or zeta-function).\n"
        "4. Extract the induced CS term and read off the level shift.\n",
    )
    mcp_server.aitp_switch_l3_activity(tmp, TOPIC_SLUG, "derive")

    # derive (was "analysis")
    mcp_server._write_md(
        tr / "L3" / "derive" / "active_derivation.md",
        {"artifact_kind": "l3_active_derivation", "activity": "derive",
         "derivation_count": 1, "all_steps_justified": "yes"},
        "# Active Derivation\n\n"
        "## Derivation Chains\n"
        "One-loop determinant yields Delta_k = N*sgn(m)/2.\n\n"
        "## Step-by-Step Trace\n"
        "Starting from the Dirac operator D = gamma^mu(i*d_mu + A_mu) in 2+1D, "
        "we compute log det(D) using proper-time regularization. The Pauli-Villars "
        "subtraction isolates the CS term yielding Delta_k = N*sgn(m)/2.\n",
    )
    mcp_server.aitp_switch_l3_activity(tmp, TOPIC_SLUG, "integrate")

    # integrate
    mcp_server._write_md(
        tr / "L3" / "integrate" / "active_integration.md",
        {"artifact_kind": "l3_active_integration", "activity": "integrate",
         "integration_statement": "Level shift confirmed for all N",
         "findings": "Delta_k = N*sgn(m)/2, consistency requires k+N/2 in Z"},
        "# Active Integration\n\n"
        "## Integration Statement\n"
        "The result integrates cleanly with the known N=1 case (Redlich) and "
        "generalizes to arbitrary N in the fundamental representation.\n\n"
        "## Findings\n"
        "Delta_k = N*sgn(m)/2. Gauge invariance of the total CS action requires "
        "k + N*sgn(m)/2 in Z, rendering the theory inconsistent for odd N unless "
        "k is appropriately shifted.\n",
    )
    mcp_server.aitp_switch_l3_activity(tmp, TOPIC_SLUG, "distill")

    # distill
    mcp_server._write_md(
        tr / "L3" / "distill" / "active_distillation.md",
        {"artifact_kind": "l3_active_distillation", "activity": "distill",
         "distilled_claim": "Delta_k = N*sgn(m)/2",
         "evidence_summary": "One-loop determinant via Pauli-Villars"},
        "# Active Distillation\n\n"
        "## Distilled Claim\n"
        "The one-loop effective action of N massless Dirac fermions in the "
        "fundamental of U(N) CS theory at level k produces a level shift "
        "Delta_k = N*sgn(m)/2, rendering the theory inconsistent unless "
        "N*sgn(m)/2 is an integer.\n\n"
        "## Evidence Summary\n"
        "Computed via Pauli-Villars regularization of the fermion determinant. "
        "Cross-checked against Redlich N=1 result and Witten's general framework.\n",
    )


def _l4_validation_and_review(tmp: str, repo_root: Path) -> None:
    """Turn 7: create validation contract and submit L4 review."""
    tr = repo_root / "topics" / TOPIC_SLUG
    # Set stage to L4 for promotion gate
    state_fm, state_body = mcp_server._parse_md(tr / "state.md")
    state_fm["stage"] = "L4"
    mcp_server._write_md(tr / "state.md", state_fm, state_body)

    mcp_server.aitp_submit_candidate(
        tmp, TOPIC_SLUG, "cand-cs-anomaly",
        "The one-loop effective action of N massless Dirac fermions in the "
        "fundamental of U(N) CS theory at level k produces a level shift "
        "Delta_k = N*sgn(m)/2.",
        "Pauli-Villars regularization of fermion determinant, cross-checked "
        "against Redlich N=1 and Witten's general framework.",
    )

    mcp_server.aitp_submit_l4_review(
        tmp, TOPIC_SLUG, "cand-cs-anomaly",
        "pass",
        "All checks passed. Dimensional consistency: CS action has correct "
        "mass dimension. Limiting case: N=1 reproduces Redlich's result. "
        "Correspondence: large-k limit recovers classical CS. Symmetry: "
        "parity transformation flips sgn(m), consistent with anomaly structure.",
        devils_advocate="CS anomaly may receive corrections from higher-order terms in 1/m expansion.",
        check_results={
            "dimensional_consistency": "pass: CS action has correct mass dimension",
            "limiting_case_check": "pass: N=1 limit reproduces Redlich",
            "correspondence_check": "pass: large-k limit recovers classical CS",
        },
    )


def _promote_candidate_and_create_tex(tmp: str, repo_root: Path) -> None:
    """Turn 8: promote candidate to L2 and create flow_notebook.tex."""
    tr = repo_root / "topics" / TOPIC_SLUG

    mcp_server.aitp_request_promotion(tmp, TOPIC_SLUG, "cand-cs-anomaly")
    mcp_server.aitp_resolve_promotion_gate(
        tmp, TOPIC_SLUG, "cand-cs-anomaly", "approve",
    )
    mcp_server.aitp_promote_candidate(tmp, TOPIC_SLUG, "cand-cs-anomaly")



class ScenarioAEndToEndTest(unittest.TestCase):
    """Full golden-path test: L1 -> L3 -> L4(pass) -> L2(promotion)."""

    def test_turn1_bootstrap_creates_topic(self):
        with tempfile.TemporaryDirectory() as tmp:
            repo_root = _bootstrap_and_register_sources(tmp)
            tr = repo_root / "topics" / TOPIC_SLUG
            self.assertTrue(tr.exists())
            self.assertTrue((tr / "state.md").exists())

            state_fm, _ = mcp_server._parse_md(tr / "state.md")
            self.assertEqual(state_fm["lane"], LANE)
            self.assertEqual(state_fm["stage"], "L1")

    def test_turn2_three_sources_registered(self):
        with tempfile.TemporaryDirectory() as tmp:
            repo_root = _bootstrap_and_register_sources(tmp)
            tr = repo_root / "topics" / TOPIC_SLUG

            sources = mcp_server.aitp_list_sources(tmp, TOPIC_SLUG)
            self.assertGreaterEqual(len(sources), 3)

    def test_turn3_l1_gate_ready_after_filling(self):
        with tempfile.TemporaryDirectory() as tmp:
            _bootstrap_and_register_sources(tmp)
            brief = mcp_server.aitp_get_execution_brief(tmp, TOPIC_SLUG)
            self.assertEqual(brief["gate_status"], "ready")

            for artifact in [
                "question_contract.md", "source_basis.md",
                "convention_snapshot.md", "derivation_anchor_map.md",
                "contradiction_register.md",
            ]:
                repo_root = Path(tmp)
                path = repo_root / "topics" / TOPIC_SLUG / "L1" / artifact
                self.assertTrue(path.exists(), f"L1/{artifact} missing")

    def test_turn4_l3_starts_in_ideate(self):
        with tempfile.TemporaryDirectory() as tmp:
            repo_root = _bootstrap_and_register_sources(tmp)
            mcp_server.aitp_advance_to_l3(tmp, TOPIC_SLUG)
            brief = mcp_server.aitp_get_execution_brief(tmp, TOPIC_SLUG)
            self.assertEqual(brief["stage"], "L3")
            self.assertEqual(brief["l3_subplane"], "ideate")

    def test_turn5_activities_traverse(self):
        with tempfile.TemporaryDirectory() as tmp:
            repo_root = _bootstrap_and_register_sources(tmp)
            _advance_l3_all_activities(tmp, repo_root)

            brief = mcp_server.aitp_get_execution_brief(tmp, TOPIC_SLUG)
            self.assertEqual(brief["l3_subplane"], "distill")

            tr = repo_root / "topics" / TOPIC_SLUG
            for act in ["ideate", "plan", "derive", "integrate", "distill"]:
                self.assertTrue(
                    (tr / "L3" / act).exists(),
                    f"L3/{act} directory missing",
                )

    def test_turn6_candidate_submitted(self):
        with tempfile.TemporaryDirectory() as tmp:
            repo_root = _bootstrap_and_register_sources(tmp)
            _advance_l3_all_activities(tmp, repo_root)

            mcp_server.aitp_submit_candidate(
                tmp, TOPIC_SLUG, "cand-cs-anomaly",
                "Delta_k = N*sgn(m)/2",
                "One-loop determinant via Pauli-Villars",
            )

            tr = repo_root / "topics" / TOPIC_SLUG
            cand_path = tr / "L3" / "candidates" / "cand-cs-anomaly.md"
            self.assertTrue(cand_path.exists())
            fm, _ = mcp_server._parse_md(cand_path)
            self.assertEqual(fm["candidate_id"], "cand-cs-anomaly")

    def test_turn7_l4_review_pass(self):
        with tempfile.TemporaryDirectory() as tmp:
            repo_root = _bootstrap_and_register_sources(tmp)
            _advance_l3_all_activities(tmp, repo_root)
            _l4_validation_and_review(tmp, repo_root)

            tr = repo_root / "topics" / TOPIC_SLUG

            review_path = tr / "L4" / "reviews" / "cand-cs-anomaly.md"
            self.assertTrue(review_path.exists())
            r_fm, _ = mcp_server._parse_md(review_path)
            self.assertEqual(r_fm["outcome"], "pass")

    def test_turn8_l2_promotion_succeeds(self):
        with tempfile.TemporaryDirectory() as tmp:
            repo_root = _bootstrap_and_register_sources(tmp)
            _advance_l3_all_activities(tmp, repo_root)
            _l4_validation_and_review(tmp, repo_root)
            _promote_candidate_and_create_tex(tmp, repo_root)

            # Candidate was promoted to L2
            from brain.mcp_server import _global_l2_path
            global_l2 = _global_l2_path(tmp)
            if global_l2.is_dir():
                nodes_dir = global_l2 / "graph" / "nodes"
                if nodes_dir.is_dir():
                    nodes = list(nodes_dir.glob("*.md"))
                    self.assertGreaterEqual(len(nodes), 1)

    def test_full_flow_verification_checkpoints(self):
        """All verification checkpoints from Scenario A in e2e_benchmark_scenarios.md."""
        with tempfile.TemporaryDirectory() as tmp:
            repo_root = _bootstrap_and_register_sources(tmp)
            _advance_l3_all_activities(tmp, repo_root)
            _l4_validation_and_review(tmp, repo_root)
            _promote_candidate_and_create_tex(tmp, repo_root)

            tr = repo_root / "topics" / TOPIC_SLUG

            # Checkpoint 2: L3/candidates/ has at least 1 candidate
            candidates = list((tr / "L3" / "candidates").glob("*.md"))
            self.assertGreaterEqual(len(candidates), 1)

            # Checkpoint 3: L4/reviews/ has a review with outcome=pass
            reviews = list((tr / "L4" / "reviews").glob("*.md"))
            self.assertGreaterEqual(len(reviews), 1)

            # Checkpoint 5: flow_notebook.tex exists at topic root
            tex_path = tr / "flow_notebook.tex"
            self.assertTrue(tex_path.exists())
            tex_content = tex_path.read_text(encoding="utf-8")
            self.assertIn("Level Shift", tex_content)

            # Checkpoint 6: runtime/log.md has key events
            log_path = tr / "runtime" / "log.md"
            self.assertTrue(log_path.exists())
            log_content = log_path.read_text(encoding="utf-8")
            for event in ["bootstrapped", "L4 review"]:
                self.assertIn(
                    event, log_content,
                    f"runtime/log.md missing event: {event}",
                )

            # Lane preserved throughout
            state_fm, _ = mcp_server._parse_md(tr / "state.md")
            self.assertEqual(state_fm["lane"], LANE)

    def test_convention_snapshot_has_cs_normalization(self):
        with tempfile.TemporaryDirectory() as tmp:
            repo_root = _bootstrap_and_register_sources(tmp)
            tr = repo_root / "topics" / TOPIC_SLUG
            conv_fm, conv_body = mcp_server._parse_md(
                tr / "L1" / "convention_snapshot.md",
            )
            self.assertIn("k/(4pi)", conv_body)

    def test_source_types_recorded(self):
        with tempfile.TemporaryDirectory() as tmp:
            _bootstrap_and_register_sources(tmp)
            sources = mcp_server.aitp_list_sources(tmp, TOPIC_SLUG)
            types = {s.get("type", "") for s in sources}
            self.assertIn("paper", types)
            self.assertIn("book", types)


if __name__ == "__main__":
    unittest.main()
