"""Tests for the Evolve feature."""

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

import pytest

from alive.core.evolve import (
    EvolutionSuggestion,
    _parse_suggestion,
    apply_evolution,
    rollback_skill,
)


@pytest.fixture
def tmp_skills_dir():
    with tempfile.TemporaryDirectory() as d:
        skill_dir = Path(d) / "slow_skill"
        skill_dir.mkdir()
        (skill_dir / "skill.py").write_text(
            'def run(n: int = 10) -> dict:\n'
            '    """A skill."""\n'
            '    return {"result": n * 2, "success": True}\n'
        )
        (skill_dir / "metadata.json").write_text(json.dumps({
            "name": "slow_skill",
            "description": "doubles a number",
            "times_used": 5,
            "success_count": 4,
            "fail_count": 1,
            "confidence_score": 0.62,
            "dependencies": [],
            "input_schema": {"n": "int"},
            "output_schema": {"result": "int"},
            "sample_input": {"n": 10},
        }))
        yield Path(d)


class TestParseSuggestion:
    def test_parses_valid_response(self):
        response = """
```reason
slow
```

```summary
Use list comprehension instead of loop for 3x speedup.
```

```confidence
0.85
```

```python improved
def run(n: int = 10) -> dict:
    return {"result": n * 2, "success": True}
```
"""
        result = _parse_suggestion("test", "original code", response)
        assert result is not None
        assert result.reason == "slow"
        assert result.confidence == 0.85
        assert "n * 2" in result.improved_code
        assert result.original_code == "original code"

    def test_returns_none_for_low_confidence(self):
        response = """
```reason
general
```

```summary
Minor style improvement.
```

```confidence
0.2
```

```python improved
def run(): pass
```
"""
        result = _parse_suggestion("test", "code", response)
        assert result is None

    def test_returns_none_for_missing_code(self):
        response = """
```reason
slow
```

```summary
Something.
```

```confidence
0.9
```
"""
        result = _parse_suggestion("test", "code", response)
        assert result is None

    def test_clamps_confidence(self):
        response = """
```reason
general
```

```summary
Improvement.
```

```confidence
1.5
```

```python improved
def run(): return {}
```
"""
        result = _parse_suggestion("test", "code", response)
        assert result is not None
        assert result.confidence == 1.0


class TestApplyEvolution:
    def test_applies_valid_improvement(self, tmp_skills_dir):
        suggestion = EvolutionSuggestion(
            skill_name="slow_skill",
            reason="slow",
            summary="Optimized math",
            improved_code=(
                'def run(n: int = 10) -> dict:\n'
                '    return {"result": n * 2, "success": True}\n'
            ),
            original_code="old code",
            confidence=0.9,
        )

        result = apply_evolution(suggestion, skills_dir=tmp_skills_dir)

        assert result.success
        assert result.version == 1

        # Check backup was created
        backup = tmp_skills_dir / "slow_skill" / "skill_v0.py"
        assert backup.exists()

        # Check metadata was updated
        meta = json.loads((tmp_skills_dir / "slow_skill" / "metadata.json").read_text())
        assert meta["evolution_version"] == 1
        assert len(meta["evolution_history"]) == 1
        assert meta["evolution_history"][0]["reason"] == "slow"

    def test_rollback_on_test_failure(self, tmp_skills_dir):
        suggestion = EvolutionSuggestion(
            skill_name="slow_skill",
            reason="general",
            summary="Bad change",
            improved_code="def run(): raise RuntimeError('broken')",
            original_code="old code",
            confidence=0.8,
        )

        result = apply_evolution(suggestion, skills_dir=tmp_skills_dir)

        assert not result.success
        assert "failed testing" in result.error

        # Backup should NOT exist (was cleaned up)
        backup = tmp_skills_dir / "slow_skill" / "skill_v0.py"
        assert not backup.exists()

    def test_missing_skill_returns_failure(self, tmp_skills_dir):
        suggestion = EvolutionSuggestion(
            skill_name="ghost_skill",
            reason="general",
            summary="Doesn't exist",
            improved_code="def run(): pass",
            original_code="",
            confidence=0.9,
        )

        result = apply_evolution(suggestion, skills_dir=tmp_skills_dir)
        assert not result.success
        assert "not found" in result.error


class TestRollbackSkill:
    def test_rollback_restores_previous_version(self, tmp_skills_dir):
        skill_dir = tmp_skills_dir / "slow_skill"
        original_code = (skill_dir / "skill.py").read_text()

        # Simulate an evolution
        (skill_dir / "skill_v0.py").write_text(original_code)
        (skill_dir / "skill.py").write_text("def run(): return {'evolved': True}")

        meta = json.loads((skill_dir / "metadata.json").read_text())
        meta["evolution_version"] = 1
        meta["evolution_history"] = [{"version": 1, "reason": "slow"}]
        (skill_dir / "metadata.json").write_text(json.dumps(meta))

        result = rollback_skill("slow_skill", skills_dir=tmp_skills_dir)
        assert result is True

        # Code should be restored
        restored = (skill_dir / "skill.py").read_text()
        assert restored == original_code

    def test_rollback_no_history(self, tmp_skills_dir):
        result = rollback_skill("slow_skill", skills_dir=tmp_skills_dir)
        assert result is False

    def test_rollback_missing_skill(self, tmp_skills_dir):
        result = rollback_skill("nonexistent", skills_dir=tmp_skills_dir)
        assert result is False
