from __future__ import annotations

import pytest

from app.core.config import settings
from app.intent.repo_classifier import RepoClassifierResult
from app.models.repos import RepoEntry, RepoStages
from app.models.repo_resolution import RepoCandidate
from app.services.repo_resolver import RepoResolver, resolve_clarification_reply


def _repo(repo_id: str) -> RepoEntry:
    return RepoEntry(
        repo_id=repo_id,
        latest_snapshot_id="snap_1",
        indexing_status="completed",
        stages=RepoStages(embedding="completed"),
    )


class FakeRepoRepository:
    def __init__(self, repos: list[RepoEntry]):
        self._repos = repos

    async def list_repos(self):
        return self._repos


@pytest.fixture(autouse=True)
def disable_llm_resolver(monkeypatch):
    monkeypatch.setattr(settings, "REPO_RESOLVER_LLM_ENABLED", False)
    monkeypatch.setattr(settings, "DEFAULT_REPO_ID", "")


@pytest.mark.asyncio
async def test_resolve_explicit_repo_id():
    repos = [_repo("ad/a.com"), _repo("ad/b.com")]
    resolver = RepoResolver(repo_repository=FakeRepoRepository(repos))  # type: ignore[arg-type]
    result = await resolver.resolve("Where is Foo?", "ad/a.com")
    assert result.is_resolved
    assert result.repo_id == "ad/a.com"


@pytest.mark.asyncio
async def test_resolve_unknown_repo_raises():
    resolver = RepoResolver(repo_repository=FakeRepoRepository([_repo("ad/a.com")]))  # type: ignore[arg-type]
    with pytest.raises(LookupError, match="Unknown or unindexed"):
        await resolver.resolve("test?", "ad/missing.com")


@pytest.mark.asyncio
async def test_resolve_single_repo_auto_pick():
    resolver = RepoResolver(repo_repository=FakeRepoRepository([_repo("ad/only.com")]))  # type: ignore[arg-type]
    result = await resolver.resolve("Where is Foo?", None)
    assert result.is_resolved
    assert result.repo_id == "ad/only.com"


@pytest.mark.asyncio
async def test_resolve_slug_match_in_question():
    repos = [
        _repo("ad/adpilot-indexing-commit-intel.com"),
        _repo("ad/adpilot-indexing-code-parser.com"),
    ]
    resolver = RepoResolver(repo_repository=FakeRepoRepository(repos))  # type: ignore[arg-type]
    result = await resolver.resolve(
        "In commit-intel, where is ProcessDelta defined?",
        None,
    )
    assert result.is_resolved
    assert result.repo_id == "ad/adpilot-indexing-commit-intel.com"


@pytest.mark.asyncio
async def test_resolve_ambiguous_returns_clarification():
    repos = [
        _repo("ad/adpilot-indexing-commit-intel.com"),
        _repo("ad/adpilot-indexing-code-parser.com"),
    ]
    resolver = RepoResolver(repo_repository=FakeRepoRepository(repos))  # type: ignore[arg-type]
    result = await resolver.resolve("Where is ProcessDelta defined?", None)
    assert not result.is_resolved
    assert result.clarification is not None
    assert result.clarification.status == "repo_clarification_required"
    assert len(result.clarification.candidates) == 2


@pytest.mark.asyncio
async def test_resolve_default_repo_env(monkeypatch):
    monkeypatch.setattr(settings, "DEFAULT_REPO_ID", "ad/adpilot-indexing-code-parser.com")
    repos = [
        _repo("ad/adpilot-indexing-commit-intel.com"),
        _repo("ad/adpilot-indexing-code-parser.com"),
    ]
    resolver = RepoResolver(repo_repository=FakeRepoRepository(repos))  # type: ignore[arg-type]
    result = await resolver.resolve("Where is ProcessDelta defined?", None)
    assert result.is_resolved
    assert result.repo_id == "ad/adpilot-indexing-code-parser.com"


@pytest.mark.asyncio
async def test_resolve_llm_high_confidence(monkeypatch):
    monkeypatch.setattr(settings, "REPO_RESOLVER_LLM_ENABLED", True)

    class FakeClassifier:
        async def classify(self, question, candidates):
            return RepoClassifierResult(
                selected_repo_id="ad/adpilot-indexing-commit-intel.com",
                confidence=0.85,
            )

    repos = [
        _repo("ad/adpilot-indexing-commit-intel.com"),
        _repo("ad/adpilot-indexing-code-parser.com"),
    ]
    resolver = RepoResolver(
        repo_repository=FakeRepoRepository(repos),  # type: ignore[arg-type]
        repo_classifier=FakeClassifier(),  # type: ignore[arg-type]
    )
    result = await resolver.resolve("Where is ProcessDelta defined?", None)
    assert result.is_resolved
    assert result.repo_id == "ad/adpilot-indexing-commit-intel.com"


@pytest.mark.asyncio
async def test_resolve_llm_low_confidence_clarifies(monkeypatch):
    monkeypatch.setattr(settings, "REPO_RESOLVER_LLM_ENABLED", True)

    class FakeClassifier:
        async def classify(self, question, candidates):
            return RepoClassifierResult(
                selected_repo_id="ad/adpilot-indexing-commit-intel.com",
                confidence=0.4,
                clarification_message="Which service do you mean?",
            )

    repos = [
        _repo("ad/adpilot-indexing-commit-intel.com"),
        _repo("ad/adpilot-indexing-code-parser.com"),
    ]
    resolver = RepoResolver(
        repo_repository=FakeRepoRepository(repos),  # type: ignore[arg-type]
        repo_classifier=FakeClassifier(),  # type: ignore[arg-type]
    )
    result = await resolver.resolve("Where is ProcessDelta defined?", None)
    assert not result.is_resolved
    assert result.clarification is not None
    assert "Which service" in result.clarification.message


def test_resolve_clarification_reply_matches_slug():
    candidates = [
        RepoCandidate(
            repo_id="ad/adpilot-indexing-commit-intel.com",
            label="adpilot-indexing-commit-intel.com",
        )
    ]
    repo_id = resolve_clarification_reply("commit intel", candidates)
    assert repo_id == "ad/adpilot-indexing-commit-intel.com"
