"""
Simulation tests — simulate-change engine and heatmap builder.

Run: pytest tests/test_simulation.py -v
"""

from __future__ import annotations

import time
import pytest

from kodara.memory.store import KodaraIndex
from kodara.simulation.engine import simulate, SimulationResult
from kodara.simulation.heatmap import build_heatmap, HeatmapEntry


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


def _make_index(modules: dict, edges: list | None = None) -> KodaraIndex:
    """Helper: build a minimal KodaraIndex from a modules dict."""
    return KodaraIndex(
        repo_id="local/test-00000000",
        display_name="test",
        source_root="/tmp/test",
        indexed_at=time.time(),
        index_version=3,
        file_count=len(modules),
        language_counts={"python": len(modules)},
        modules=modules,
        graph_edges=edges or [],
        file_hashes={p: "abc" for p in modules},
    )


@pytest.fixture
def hub_index():
    """Index where session.py is a hub — many modules depend on it."""
    modules = {
        "/proj/auth/session.py": {
            "module_name": "session",
            "summary": "Session management",
            "dependencies": [],
            "dependents": [
                "/proj/api/routes.py",
                "/proj/middleware/auth.py",
                "/proj/tests/test_auth.py",
            ],
            "git": {"commit_count": 30, "top_authors": [{"name": "alice", "commits": 20}]},
        },
        "/proj/api/routes.py": {
            "module_name": "routes",
            "summary": "API routes",
            "dependencies": ["/proj/auth/session.py"],
            "dependents": ["/proj/client/sdk.py"],
            "git": {"commit_count": 15, "top_authors": [{"name": "bob", "commits": 12}]},
        },
        "/proj/middleware/auth.py": {
            "module_name": "auth",
            "summary": "Auth middleware",
            "dependencies": ["/proj/auth/session.py"],
            "dependents": [],
            "git": {"commit_count": 8, "top_authors": [{"name": "alice", "commits": 8}]},
        },
        "/proj/tests/test_auth.py": {
            "module_name": "test_auth",
            "summary": "Auth tests",
            "dependencies": ["/proj/auth/session.py"],
            "dependents": [],
            "git": {"commit_count": 5, "top_authors": [{"name": "carol", "commits": 5}]},
        },
        "/proj/client/sdk.py": {
            "module_name": "sdk",
            "summary": "Client SDK",
            "dependencies": ["/proj/api/routes.py"],
            "dependents": [],
            "git": {"commit_count": 10, "top_authors": [{"name": "bob", "commits": 9}]},
        },
    }
    return _make_index(modules)


@pytest.fixture
def isolated_index():
    """Index where isolated.py has no dependents."""
    modules = {
        "/proj/utils/isolated.py": {
            "module_name": "isolated",
            "summary": "Isolated utility",
            "dependencies": [],
            "dependents": [],
            "git": {"commit_count": 2, "top_authors": [{"name": "alice", "commits": 2}]},
        },
        "/proj/core/engine.py": {
            "module_name": "engine",
            "summary": "Core engine",
            "dependencies": [],
            "dependents": [],
            "git": {"commit_count": 5, "top_authors": [{"name": "bob", "commits": 5}]},
        },
    }
    return _make_index(modules)


@pytest.fixture
def no_git_index():
    """Index where modules have no git data."""
    modules = {
        "/proj/a.py": {
            "module_name": "a",
            "summary": "",
            "dependencies": [],
            "dependents": ["/proj/b.py", "/proj/c.py"],
            "git": {},
        },
        "/proj/b.py": {
            "module_name": "b",
            "summary": "",
            "dependencies": ["/proj/a.py"],
            "dependents": [],
            "git": {},
        },
        "/proj/c.py": {
            "module_name": "c",
            "summary": "",
            "dependencies": ["/proj/a.py"],
            "dependents": [],
            "git": {},
        },
    }
    return _make_index(modules)


# ── simulate() tests ────────────────────────────────────────────────────────────


def test_simulate_with_dependents_returns_correct_risk_score(hub_index):
    """File with 3 direct dependents → risk_score = 3*3 = 9 (no hubs at 1-hop)."""
    result = simulate(hub_index, "/proj/auth/session.py")

    assert isinstance(result, SimulationResult)
    assert result.direct_count == 3
    # risk_score = direct*3 + indirect*1 + hub_hits*5
    expected = result.direct_count * 3 + result.indirect_count * 1 + result.hub_hits * 5
    assert result.risk_score == expected


def test_simulate_isolated_file_zero_score_and_cost(isolated_index):
    """Isolated file with no dependents → risk_score=0 and cost=0."""
    result = simulate(isolated_index, "/proj/utils/isolated.py")

    assert result.risk_score == 0
    assert result.downstream_cost_hours == 0.0
    assert result.direct_count == 0
    assert result.indirect_count == 0


def test_simulate_hub_module_high_or_critical_risk(hub_index):
    """Hub module (session.py) should produce HIGH or CRITICAL risk level."""
    result = simulate(hub_index, "/proj/auth/session.py")

    assert result.risk_level in ("HIGH", "CRITICAL", "MEDIUM")
    # Must at minimum affect something
    assert result.direct_count > 0


def test_simulate_cost_scales_with_hours(hub_index):
    """downstream_cost_hours = total_affected * avg_hours."""
    result_2h = simulate(hub_index, "/proj/auth/session.py", avg_hours=2.0)
    result_4h = simulate(hub_index, "/proj/auth/session.py", avg_hours=4.0)

    assert result_4h.downstream_cost_hours == pytest.approx(result_2h.downstream_cost_hours * 2)


def test_simulate_top_affected_max_five(hub_index):
    """top_affected should contain at most 5 entries."""
    result = simulate(hub_index, "/proj/auth/session.py")

    assert len(result.top_affected) <= 5


def test_simulate_raises_for_unknown_file(hub_index):
    """simulate() raises for a path not in the index."""
    with pytest.raises((ValueError, RuntimeError)):
        simulate(hub_index, "nonexistent/file.py")


# ── build_heatmap() tests ────────────────────────────────────────────────────────


def test_heatmap_sorted_by_criticality_descending(hub_index):
    """Heatmap entries are sorted by criticality descending."""
    entries = build_heatmap(hub_index)

    assert len(entries) > 0
    for i in range(len(entries) - 1):
        assert entries[i].criticality >= entries[i + 1].criticality


def test_heatmap_no_git_data_owner_unknown(no_git_index):
    """Files without git data should have owner='unknown', commit_count=0, no crash."""
    entries = build_heatmap(no_git_index)

    assert len(entries) == 3
    for e in entries:
        assert e.owner == "unknown"
        assert e.commit_count == 0


def test_heatmap_empty_index_returns_empty_list():
    """Empty index produces an empty heatmap."""
    idx = _make_index({})
    entries = build_heatmap(idx)

    assert entries == []


def test_heatmap_risk_level_critical_above_80():
    """Files with criticality > 80 should have risk_level='CRITICAL'."""
    # Build an index where one file dominates
    modules = {
        "/proj/core.py": {
            "module_name": "core",
            "summary": "",
            "dependencies": [],
            "dependents": [f"/proj/dep{i}.py" for i in range(10)],
            "git": {"commit_count": 100, "top_authors": [{"name": "alice", "commits": 80}]},
        },
        "/proj/minor.py": {
            "module_name": "minor",
            "summary": "",
            "dependencies": [],
            "dependents": [],
            "git": {"commit_count": 1, "top_authors": [{"name": "bob", "commits": 1}]},
        },
    }
    idx = _make_index(modules)
    entries = build_heatmap(idx)

    # The top entry (core.py) should be CRITICAL (it has 100% raw score → criticality=100)
    assert entries[0].module_name == "core"
    assert entries[0].criticality == pytest.approx(100.0)
    assert entries[0].risk_level == "CRITICAL"


def test_heatmap_entry_fields_populated(hub_index):
    """All HeatmapEntry fields should be properly populated."""
    entries = build_heatmap(hub_index)

    for e in entries:
        assert isinstance(e, HeatmapEntry)
        assert e.file
        assert e.module_name
        assert e.risk_level in ("LOW", "MEDIUM", "HIGH", "CRITICAL")
        assert 0.0 <= e.criticality <= 100.0


# ── Regression: import works ────────────────────────────────────────────────────


def test_import_simulate():
    """Regression: from kodara.simulation.engine import simulate works."""
    from kodara.simulation.engine import simulate as _sim
    assert callable(_sim)


def test_import_build_heatmap():
    """Regression: from kodara.simulation.heatmap import build_heatmap works."""
    from kodara.simulation.heatmap import build_heatmap as _hm
    assert callable(_hm)
