"""Tests for Battle Mode in the skill builder."""

import json
import tempfile
from dataclasses import asdict
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from alive.core.replay import (
    BattleContestant,
    BattleLog,
    BirthStory,
    BuildAttemptRecord,
    load_birth_story,
    save_birth_story,
)
from alive.core.skill_builder import (
    BATTLE_BENCHMARK_RUNS,
    BattleResult,
    BuildResult,
    SkillBuilder,
    _APPROACH_SYSTEM,
    _BATTLE_ALT_SUFFIX,
    _BATTLE_SIMPLE_SUFFIX,
)


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


@pytest.fixture
def tmp_skills_dir():
    with tempfile.TemporaryDirectory() as d:
        skill_dir = Path(d) / "test_skill"
        skill_dir.mkdir()
        (skill_dir / "skill.py").write_text("def run(): return {}")
        yield Path(d)


def _make_build_result(name: str = "test_skill") -> BuildResult:
    return BuildResult(
        skill_name=name,
        description="A test skill",
        code='def run(n: int = 5) -> dict:\n    return {"result": n * 2, "success": True}\n',
        test_code='from skill import run\n\ndef test_it():\n    assert run(5)["result"] == 10\n',
        dependencies=[],
        input_schema={"n": "int"},
        output_schema={"result": "int", "success": "bool"},
        skill_doc="# test_skill\nA test skill.",
        sample_input={"n": 5},
    )


# ── BattleContestant dataclass tests ────────────────────────────────────────


class TestBattleContestant:
    def test_creation(self):
        c = BattleContestant(
            label="A",
            approach="regex line scan",
            code="def run(): pass",
            passed=True,
            avg_time_ms=120.5,
        )
        assert c.label == "A"
        assert c.approach == "regex line scan"
        assert c.passed is True
        assert c.avg_time_ms == 120.5
        assert c.error is None

    def test_failed_contestant(self):
        c = BattleContestant(
            label="B",
            approach="pandas approach",
            code="def run(): raise ValueError()",
            passed=False,
            error="ValueError: something broke",
        )
        assert c.passed is False
        assert "ValueError" in c.error

    def test_defaults(self):
        c = BattleContestant(
            label="A", approach="basic", code="pass", passed=True
        )
        assert c.avg_time_ms == 0.0
        assert c.error is None


# ── BattleLog dataclass tests ───────────────────────────────────────────────


class TestBattleLog:
    def test_both_passed_winner_a(self):
        bl = BattleLog(
            contestant_a=BattleContestant(
                label="A", approach="simple", code="a", passed=True, avg_time_ms=100
            ),
            contestant_b=BattleContestant(
                label="B", approach="fancy", code="b", passed=True, avg_time_ms=200
            ),
            winner="A",
            speedup=2.0,
        )
        assert bl.winner == "A"
        assert bl.speedup == 2.0

    def test_fallback(self):
        bl = BattleLog(
            contestant_a=BattleContestant(
                label="A", approach="bad1", code="a", passed=False, error="err"
            ),
            contestant_b=BattleContestant(
                label="B", approach="bad2", code="b", passed=False, error="err"
            ),
            winner="fallback",
        )
        assert bl.winner == "fallback"
        assert bl.speedup == 0.0

    def test_serialization_roundtrip(self):
        bl = BattleLog(
            contestant_a=BattleContestant(
                label="A", approach="simple math", code="def run(): return 1",
                passed=True, avg_time_ms=50.0,
            ),
            contestant_b=BattleContestant(
                label="B", approach="numpy approach", code="def run(): return 2",
                passed=True, avg_time_ms=80.0,
            ),
            winner="A",
            speedup=1.6,
        )
        data = asdict(bl)
        restored = BattleLog(
            contestant_a=BattleContestant(**data["contestant_a"]),
            contestant_b=BattleContestant(**data["contestant_b"]),
            winner=data["winner"],
            speedup=data["speedup"],
        )
        assert restored.winner == "A"
        assert restored.contestant_a.approach == "simple math"
        assert restored.contestant_b.avg_time_ms == 80.0


# ── BattleResult dataclass tests ────────────────────────────────────────────


class TestBattleResult:
    def test_creation(self):
        br = BattleResult(
            winner=_make_build_result(),
            battle_log=BattleLog(
                contestant_a=BattleContestant(
                    label="A", approach="x", code="a", passed=True
                ),
                contestant_b=BattleContestant(
                    label="B", approach="y", code="b", passed=False
                ),
                winner="A",
            ),
        )
        assert br.used_fallback is False
        assert br.winner.skill_name == "test_skill"

    def test_fallback_flag(self):
        br = BattleResult(
            winner=_make_build_result(),
            battle_log=BattleLog(
                contestant_a=BattleContestant(
                    label="A", approach="x", code="a", passed=False
                ),
                contestant_b=BattleContestant(
                    label="B", approach="y", code="b", passed=False
                ),
                winner="fallback",
            ),
            used_fallback=True,
        )
        assert br.used_fallback is True
        assert br.battle_log.winner == "fallback"


# ── Birth story with battle log persistence ─────────────────────────────────


class TestBirthStoryWithBattle:
    def test_save_and_load_with_battle_log(self, tmp_skills_dir):
        story = BirthStory(
            skill_name="test_skill",
            original_request="build a test",
            attempts=[
                BuildAttemptRecord(1, "code_v1", "pass"),
            ],
            total_time_ms=5000,
            final_approach="Won in battle mode.",
            created_at="2024-01-01T00:00:00Z",
            battle_log=BattleLog(
                contestant_a=BattleContestant(
                    label="A", approach="simple loop",
                    code="def run(): return {'a': 1}",
                    passed=True, avg_time_ms=100.0,
                ),
                contestant_b=BattleContestant(
                    label="B", approach="numpy vectorized",
                    code="def run(): return {'b': 2}",
                    passed=True, avg_time_ms=200.0,
                ),
                winner="A",
                speedup=2.0,
            ),
        )

        path = save_birth_story(story, skills_dir=tmp_skills_dir)
        assert path.exists()

        loaded = load_birth_story("test_skill", skills_dir=tmp_skills_dir)
        assert loaded.battle_log is not None
        assert loaded.battle_log.winner == "A"
        assert loaded.battle_log.speedup == 2.0
        assert loaded.battle_log.contestant_a.approach == "simple loop"
        assert loaded.battle_log.contestant_a.passed is True
        assert loaded.battle_log.contestant_a.avg_time_ms == 100.0
        assert loaded.battle_log.contestant_b.approach == "numpy vectorized"
        assert loaded.battle_log.contestant_b.passed is True

    def test_save_and_load_without_battle_log(self, tmp_skills_dir):
        story = BirthStory(
            skill_name="test_skill",
            original_request="build a test",
            attempts=[BuildAttemptRecord(1, "code_v1", "pass")],
            total_time_ms=1000,
            final_approach="First try.",
            created_at="2024-01-01T00:00:00Z",
        )

        save_birth_story(story, skills_dir=tmp_skills_dir)
        loaded = load_birth_story("test_skill", skills_dir=tmp_skills_dir)
        assert loaded.battle_log is None

    def test_save_and_load_fallback_battle(self, tmp_skills_dir):
        story = BirthStory(
            skill_name="test_skill",
            original_request="build a test",
            attempts=[],
            total_time_ms=8000,
            final_approach="Fallback to normal build.",
            created_at="2024-01-01T00:00:00Z",
            battle_log=BattleLog(
                contestant_a=BattleContestant(
                    label="A", approach="bad approach",
                    code="def run(): raise Exception()",
                    passed=False, error="Exception raised",
                ),
                contestant_b=BattleContestant(
                    label="B", approach="also bad",
                    code="def run(): 1/0",
                    passed=False, error="ZeroDivisionError",
                ),
                winner="fallback",
            ),
        )

        save_birth_story(story, skills_dir=tmp_skills_dir)
        loaded = load_birth_story("test_skill", skills_dir=tmp_skills_dir)
        assert loaded.battle_log is not None
        assert loaded.battle_log.winner == "fallback"
        assert loaded.battle_log.contestant_a.passed is False
        assert loaded.battle_log.contestant_b.error == "ZeroDivisionError"


# ── SkillBuilder battle flag tests ──────────────────────────────────────────


class TestSkillBuilderBattleFlag:
    def test_battle_defaults_to_true(self):
        builder = SkillBuilder("test skill")
        assert builder._battle is True

    def test_battle_can_be_disabled(self):
        builder = SkillBuilder("test skill", battle=False)
        assert builder._battle is False


# ── Prompt suffix tests ─────────────────────────────────────────────────────


class TestBattlePrompts:
    def test_simple_suffix_mentions_simplest(self):
        assert "SIMPLEST" in _BATTLE_SIMPLE_SUFFIX
        assert "standard library" in _BATTLE_SIMPLE_SUFFIX

    def test_alt_suffix_mentions_different(self):
        assert "DIFFERENT" in _BATTLE_ALT_SUFFIX
        assert "creative" in _BATTLE_ALT_SUFFIX

    def test_approach_system_is_short(self):
        assert "2-5 word" in _APPROACH_SYSTEM

    def test_benchmark_runs_constant(self):
        assert BATTLE_BENCHMARK_RUNS >= 1
        assert BATTLE_BENCHMARK_RUNS == 3


# ── Display battle panel test ───────────────────────────────────────────────


class TestDisplayBattlePanel:
    def test_display_panel_doesnt_crash(self):
        """The display method should not raise even with edge-case inputs."""
        from rich.console import Console

        console = Console(file=open("/dev/null", "w"), force_terminal=False)
        builder = SkillBuilder("test", console=console)

        # Both passed
        builder._display_battle_panel(
            "test_skill", "approach A", "approach B",
            True, True, 100.0, 200.0, None, None,
        )

        # Only A passed
        builder._display_battle_panel(
            "test_skill", "approach A", "approach B",
            True, False, 100.0, 0.0, None, "some error",
        )

        # Only B passed
        builder._display_battle_panel(
            "test_skill", "approach A", "approach B",
            False, True, 0.0, 150.0, "err A", None,
        )

    def test_display_panel_with_zero_times(self):
        """Edge case: zero avg times should not cause ZeroDivisionError."""
        from rich.console import Console

        console = Console(file=open("/dev/null", "w"), force_terminal=False)
        builder = SkillBuilder("test", console=console)

        # Both passed with zero times
        builder._display_battle_panel(
            "test_skill", "approach A", "approach B",
            True, True, 0.0, 0.0, None, None,
        )


# ── Replay display with battle log ──────────────────────────────────────────


class TestReplayDisplayWithBattle:
    def test_display_birth_story_with_battle(self):
        from rich.console import Console

        from alive.core.replay import display_birth_story

        story = BirthStory(
            skill_name="battled_skill",
            original_request="test battle display",
            attempts=[BuildAttemptRecord(1, "def run(): return {}", "pass")],
            total_time_ms=3000,
            final_approach="Won in battle mode.",
            battle_log=BattleLog(
                contestant_a=BattleContestant(
                    label="A", approach="simple",
                    code="def run(): return {'a': 1}",
                    passed=True, avg_time_ms=50.0,
                ),
                contestant_b=BattleContestant(
                    label="B", approach="complex",
                    code="def run(): return {'b': 2}",
                    passed=True, avg_time_ms=100.0,
                ),
                winner="A",
                speedup=2.0,
            ),
        )

        console = Console(file=open("/dev/null", "w"), force_terminal=False)
        # Should not crash
        display_birth_story(story, console=console)

    def test_display_birth_story_with_fallback_battle(self):
        from rich.console import Console

        from alive.core.replay import display_birth_story

        story = BirthStory(
            skill_name="fallback_skill",
            original_request="test fallback display",
            attempts=[],
            total_time_ms=5000,
            final_approach="Fallback.",
            battle_log=BattleLog(
                contestant_a=BattleContestant(
                    label="A", approach="bad1", code="a", passed=False, error="err"
                ),
                contestant_b=BattleContestant(
                    label="B", approach="bad2", code="b", passed=False, error="err"
                ),
                winner="fallback",
            ),
        )

        console = Console(file=open("/dev/null", "w"), force_terminal=False)
        display_birth_story(story, console=console)
