"""
Tests for kodara.intelligence — why, bus_factor, debt.

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

from __future__ import annotations

import time
import tempfile
import textwrap
from pathlib import Path

import pytest

from kodara.memory.store import MemoryStore, KodaraIndex, INDEX_VERSION
from kodara.intelligence.why import get_why, WhyResult
from kodara.intelligence.bus_factor import get_bus_factor, BusFactorRisk
from kodara.intelligence.debt import get_debt, DebtItem


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


def _make_index(modules: dict, graph_edges: list | None = None) -> KodaraIndex:
    """Helper: build a minimal KodaraIndex from modules dict."""
    return KodaraIndex(
        repo_id="local/test-abc12345",
        display_name="test",
        source_root="/fake/root",
        indexed_at=time.time(),
        index_version=INDEX_VERSION,
        file_count=len(modules),
        language_counts={"python": len(modules)},
        modules=modules,
        graph_edges=graph_edges or [],
        file_hashes={path: "hash" for path in modules},
    )


@pytest.fixture
def index_with_git():
    """Index where one file has git data and multiple dependents."""
    modules = {
        "/fake/root/auth.py": {
            "module_name": "auth",
            "summary": "Authentication middleware",
            "dependencies": [],
            "dependents": ["/fake/root/api.py", "/fake/root/admin.py", "/fake/root/tests.py"],
            "git": {
                "commit_count": 15,
                "last_commit": "abc12345",
                "last_author": "Alice",
                "last_date": "2024-01-15",
                "last_message": "Fix token expiry bug",
                "top_authors": [{"name": "Alice", "commits": 15}],
            },
        },
        "/fake/root/api.py": {
            "module_name": "api",
            "summary": "REST API server",
            "dependencies": ["/fake/root/auth.py"],
            "dependents": [],
            "git": {
                "commit_count": 8,
                "last_commit": "def67890",
                "last_author": "Bob",
                "last_date": "2024-01-10",
                "last_message": "Add rate limiting",
                "top_authors": [
                    {"name": "Bob", "commits": 5},
                    {"name": "Alice", "commits": 3},
                ],
            },
        },
        "/fake/root/admin.py": {
            "module_name": "admin",
            "summary": "Admin panel",
            "dependencies": ["/fake/root/auth.py"],
            "dependents": [],
            "git": {
                "commit_count": 3,
                "last_commit": "ghi11111",
                "last_author": "Bob",
                "last_date": "2024-01-05",
                "last_message": "Add user management",
                "top_authors": [{"name": "Bob", "commits": 3}],
            },
        },
        "/fake/root/tests.py": {
            "module_name": "tests",
            "summary": "Test suite",
            "dependencies": ["/fake/root/auth.py"],
            "dependents": [],
            "git": {
                "commit_count": 20,
                "last_commit": "jkl22222",
                "last_author": "Alice",
                "last_date": "2024-01-20",
                "last_message": "Add integration tests",
                "top_authors": [
                    {"name": "Alice", "commits": 12},
                    {"name": "Bob", "commits": 8},
                ],
            },
        },
    }
    return _make_index(modules)


@pytest.fixture
def index_no_git():
    """Index with no git data at all."""
    modules = {
        "/fake/root/utils.py": {
            "module_name": "utils",
            "summary": "Utility functions",
            "dependencies": [],
            "dependents": ["/fake/root/main.py"],
            "git": {},
        },
        "/fake/root/main.py": {
            "module_name": "main",
            "summary": "Entry point",
            "dependencies": ["/fake/root/utils.py"],
            "dependents": [],
            "git": {},
        },
    }
    return _make_index(modules)


@pytest.fixture
def empty_index():
    """Completely empty index."""
    return _make_index({})


# ── Import regression ─────────────────────────────────────────────────────────


class TestImports:

    def test_import_get_why(self):
        from kodara.intelligence.why import get_why
        assert callable(get_why)

    def test_import_get_bus_factor(self):
        from kodara.intelligence.bus_factor import get_bus_factor
        assert callable(get_bus_factor)

    def test_import_get_debt(self):
        from kodara.intelligence.debt import get_debt
        assert callable(get_debt)

    def test_import_why_result(self):
        from kodara.intelligence.why import WhyResult
        assert WhyResult is not None

    def test_import_bus_factor_risk(self):
        from kodara.intelligence.bus_factor import BusFactorRisk
        assert BusFactorRisk is not None

    def test_import_debt_item(self):
        from kodara.intelligence.debt import DebtItem
        assert DebtItem is not None


# ── WhyResult tests ───────────────────────────────────────────────────────────


class TestWhy:

    def test_why_with_git_data_returns_result(self, index_with_git):
        result = get_why(index_with_git, "/fake/root/auth.py")
        assert isinstance(result, WhyResult)
        assert result.file == "/fake/root/auth.py"
        assert result.module_name == "auth"
        assert result.git_commits == 15
        assert result.git_reason == "Fix token expiry bug"
        assert len(result.git_authors) == 1
        assert result.git_authors[0]["name"] == "Alice"
        assert result.summary == "Authentication middleware"
        assert result.dependents_count == 3

    def test_why_returns_dependents_list(self, index_with_git):
        result = get_why(index_with_git, "/fake/root/auth.py")
        assert "/fake/root/api.py" in result.dependents
        assert "/fake/root/admin.py" in result.dependents

    def test_why_without_git_data_graceful(self, index_no_git):
        result = get_why(index_no_git, "/fake/root/utils.py")
        assert isinstance(result, WhyResult)
        assert result.git_commits == 0
        assert result.git_reason == ""
        assert result.git_authors == []
        # Should still return summary and dependents
        assert result.dependents_count == 1

    def test_why_unknown_file_graceful(self, index_with_git):
        # Unknown file should return a result without crashing
        result = get_why(index_with_git, "/fake/root/nonexistent.py")
        assert isinstance(result, WhyResult)
        assert result.git_commits == 0
        assert result.dependents_count == 0

    def test_why_fuzzy_match(self, index_with_git):
        # Partial path should fuzzy-match
        result = get_why(index_with_git, "auth.py")
        assert result.file == "/fake/root/auth.py"
        assert result.module_name == "auth"

    def test_why_notes_none_store(self, index_with_git):
        # notes_store=None should work without crashing
        result = get_why(index_with_git, "/fake/root/auth.py", notes_store=None)
        assert result.notes == []

    def test_why_empty_notes_store(self, index_with_git, tmp_path):
        from kodara.notes.store import NotesStore
        # Create a real NotesStore pointing to an empty temp dir
        kodara_dir = tmp_path / ".kodara"
        kodara_dir.mkdir()
        ns = NotesStore(kodara_dir)
        result = get_why(index_with_git, "/fake/root/auth.py", notes_store=ns)
        assert isinstance(result, WhyResult)
        assert result.notes == []


# ── BusFactor tests ───────────────────────────────────────────────────────────


class TestBusFactor:

    def test_sole_author_with_many_dependents_is_high_risk(self, index_with_git):
        risks = get_bus_factor(index_with_git)
        # auth.py: sole author Alice, 3 dependents → HIGH
        files = [r.file for r in risks]
        assert "/fake/root/auth.py" in files

    def test_risk_level_for_sole_author_3_deps(self, index_with_git):
        risks = get_bus_factor(index_with_git)
        auth_risk = next(r for r in risks if r.file == "/fake/root/auth.py")
        assert auth_risk.risk_level == "HIGH"
        assert auth_risk.sole_author == "Alice"
        assert auth_risk.dependents_count == 3

    def test_multi_author_file_not_in_risks(self, index_with_git):
        risks = get_bus_factor(index_with_git)
        # api.py has two authors (Bob + Alice) → not a bus factor risk
        files = [r.file for r in risks]
        assert "/fake/root/api.py" not in files

    def test_no_git_data_returns_empty(self, index_no_git):
        risks = get_bus_factor(index_no_git)
        assert risks == []

    def test_empty_index_returns_empty(self, empty_index):
        risks = get_bus_factor(empty_index)
        assert risks == []

    def test_risks_sorted_critical_first(self):
        """CRITICAL risk files should appear before HIGH/MEDIUM/LOW."""
        modules = {
            "/fake/root/core.py": {
                "module_name": "core",
                "summary": "",
                "dependencies": [],
                "dependents": [f"/fake/root/mod{i}.py" for i in range(8)],
                "git": {
                    "commit_count": 50,
                    "last_message": "core update",
                    "top_authors": [{"name": "Solo", "commits": 50}],
                },
            },
            "/fake/root/helper.py": {
                "module_name": "helper",
                "summary": "",
                "dependencies": [],
                "dependents": ["/fake/root/other.py"],
                "git": {
                    "commit_count": 5,
                    "last_message": "helper update",
                    "top_authors": [{"name": "Solo", "commits": 5}],
                },
            },
        }
        idx = _make_index(modules)
        risks = get_bus_factor(idx)
        assert len(risks) >= 2
        # core has 8 dependents → CRITICAL; helper has 1 → MEDIUM
        assert risks[0].file == "/fake/root/core.py"
        assert risks[0].risk_level == "CRITICAL"

    def test_returns_list_of_bus_factor_risk(self, index_with_git):
        risks = get_bus_factor(index_with_git)
        assert isinstance(risks, list)
        for r in risks:
            assert isinstance(r, BusFactorRisk)

    def test_sole_author_admin_with_zero_dependents_excluded(self, index_with_git):
        # admin.py: sole author Bob but 0 dependents (and not a hub)
        # Should be excluded because dependents < threshold
        risks = get_bus_factor(index_with_git)
        # admin.py has 0 dependents — should not appear
        admin_risks = [r for r in risks if r.file == "/fake/root/admin.py"]
        # admin has 0 dependents, threshold is 1, so it SHOULD be excluded
        assert len(admin_risks) == 0


# ── Debt tests ────────────────────────────────────────────────────────────────


class TestDebt:

    def test_high_commit_many_deps_high_debt(self, index_with_git):
        items = get_debt(index_with_git)
        assert isinstance(items, list)
        assert len(items) > 0
        # auth.py: 15 commits * 3 dependents = 45 → HIGH
        auth_item = next((d for d in items if d.module_name == "auth"), None)
        assert auth_item is not None
        assert auth_item.debt_score == 45
        assert auth_item.risk_level == "HIGH"

    def test_debt_sorted_descending(self, index_with_git):
        items = get_debt(index_with_git)
        scores = [d.debt_score for d in items]
        assert scores == sorted(scores, reverse=True)

    def test_empty_index_returns_empty(self, empty_index):
        items = get_debt(empty_index)
        assert items == []

    def test_no_git_all_zero_debt(self, index_no_git):
        items = get_debt(index_no_git)
        for d in items:
            assert d.debt_score == 0
            assert d.risk_level == "NONE"

    def test_debt_top_n_limit(self, index_with_git):
        items = get_debt(index_with_git, top_n=2)
        assert len(items) <= 2

    def test_debt_formula(self):
        """Verify debt_score = commit_count * dependents_count."""
        modules = {
            "/fake/root/busy.py": {
                "module_name": "busy",
                "summary": "",
                "dependencies": [],
                "dependents": ["/fake/root/a.py", "/fake/root/b.py", "/fake/root/c.py"],
                "git": {
                    "commit_count": 20,
                    "top_authors": [{"name": "Dev", "commits": 20}],
                },
            },
        }
        idx = _make_index(modules)
        items = get_debt(idx)
        assert len(items) == 1
        assert items[0].commit_count == 20
        assert items[0].dependents_count == 3
        assert items[0].debt_score == 60  # 20 * 3
        assert items[0].risk_level == "CRITICAL"

    def test_returns_list_of_debt_items(self, index_with_git):
        items = get_debt(index_with_git)
        for item in items:
            assert isinstance(item, DebtItem)

    def test_risk_levels_assigned_correctly(self):
        """Verify risk level thresholds."""
        from kodara.intelligence.debt import _risk_level
        assert _risk_level(0) == "NONE"
        assert _risk_level(1) == "LOW"
        assert _risk_level(9) == "LOW"
        assert _risk_level(10) == "MEDIUM"
        assert _risk_level(29) == "MEDIUM"
        assert _risk_level(30) == "HIGH"
        assert _risk_level(59) == "HIGH"
        assert _risk_level(60) == "CRITICAL"
        assert _risk_level(100) == "CRITICAL"
