"""Tests for vector store and embeddings."""

from __future__ import annotations

from pathlib import Path

import pytest

from redhat_oracle.core.config import OracleConfig
from redhat_oracle.core.vectordb.chunker import Chunk
from redhat_oracle.core.vectordb.embeddings import EmbeddingProvider
from redhat_oracle.core.vectordb.store import VectorStore


class FakeEmbedder(EmbeddingProvider):
    """Fake embedder that returns deterministic vectors without loading a model."""

    def __init__(self) -> None:
        super().__init__("fake-model")
        self._call_count = 0

    def _load_model(self) -> None:
        pass  # Don't load any model

    def embed(self, texts: list[str]) -> list[list[float]]:
        self._call_count += 1
        # Return deterministic vectors based on text hash
        results = []
        for text in texts:
            h = hash(text) % 1000
            vec = [float(h + i) / 1000.0 for i in range(384)]
            # Normalize
            norm = sum(v * v for v in vec) ** 0.5
            vec = [v / norm for v in vec]
            results.append(vec)
        return results

    def embed_single(self, text: str) -> list[float]:
        return self.embed([text])[0]

    @property
    def dimension(self) -> int:
        return 384


def _make_chunk(
    text: str,
    source: str = "github",
    org: str = "testorg",
    repo: str = "testrepo",
    path: str = "test.py",
    author: str = "",
    jira_issue_id: str = "",
    labels: list[str] | None = None,
) -> Chunk:
    """Create a test chunk with deterministic ID."""
    from redhat_oracle.core.vectordb.chunker import _generate_chunk_id

    return Chunk(
        chunk_id=_generate_chunk_id(source, org, repo, path, text),
        text=text,
        source=source,
        org=org,
        repo=repo,
        path=path,
        start_line=1,
        end_line=10,
        author=author,
        jira_issue_id=jira_issue_id,
        labels=labels or [],
    )


class TestEmbeddingProvider:
    def test_lazy_loading(self) -> None:
        provider = EmbeddingProvider("all-MiniLM-L6-v2")
        assert provider._model is None

    def test_embed_with_fake(self) -> None:
        embedder = FakeEmbedder()
        result = embedder.embed(["hello world"])
        assert len(result) == 1
        assert len(result[0]) == 384

    def test_embed_single(self) -> None:
        embedder = FakeEmbedder()
        result = embedder.embed_single("hello world")
        assert len(result) == 384

    def test_dimension(self) -> None:
        embedder = FakeEmbedder()
        assert embedder.dimension == 384


class TestVectorStore:
    def test_upsert_and_search(self, tmp_path: Path) -> None:
        config = OracleConfig()
        embedder = FakeEmbedder()
        store = VectorStore(config, embedder=embedder, db_path=tmp_path / "chroma")

        chunks = [
            _make_chunk("Python function for sorting data"),
            _make_chunk("JavaScript module for HTTP requests"),
            _make_chunk("Rust struct for memory management"),
        ]

        count = store.upsert_chunks(chunks)
        assert count == 3

        results = store.search("sorting algorithm", min_score=0.0)
        assert len(results) > 0
        assert all("score" in r for r in results)

    def test_upsert_empty(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")
        assert store.upsert_chunks([]) == 0

    def test_deduplication(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        chunk = _make_chunk("identical content")
        store.upsert_chunks([chunk])
        store.upsert_chunks([chunk])  # Same ID = upsert, not duplicate

        stats = store.collection_stats()
        assert stats["oracle_code"] == 1

    def test_batch_embedding(self, tmp_path: Path) -> None:
        config = OracleConfig(
            vectordb={"embeddings": {"batch_size": 2}}  # type: ignore[arg-type]
        )
        embedder = FakeEmbedder()
        store = VectorStore(config, embedder=embedder, db_path=tmp_path / "chroma")

        chunks = [_make_chunk(f"chunk {i}", path=f"file{i}.py") for i in range(5)]

        progress_calls: list[tuple[int, int]] = []

        def track_progress(batch_num: int, total: int) -> None:
            progress_calls.append((batch_num, total))

        store.upsert_chunks(chunks, progress_callback=track_progress)

        # 5 chunks / batch_size 2 = 3 batches
        assert len(progress_calls) == 3
        assert progress_calls[-1] == (3, 3)

    def test_delete_repo_chunks(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        chunks = [
            _make_chunk("chunk from repo1", repo="repo1", path="a.py"),
            _make_chunk("chunk from repo2", repo="repo2", path="b.py"),
        ]
        store.upsert_chunks(chunks)

        deleted = store.delete_repo_chunks("github", "testorg", "repo1")
        assert deleted == 1

        stats = store.collection_stats()
        assert stats["oracle_code"] == 1

    def test_reindex_repo(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        old_chunks = [
            _make_chunk("old content A", path="a.py"),
            _make_chunk("old content B", path="b.py"),
        ]
        store.upsert_chunks(old_chunks)
        assert store.collection_stats()["oracle_code"] == 2

        new_chunks = [
            _make_chunk("new content X", path="x.py"),
            _make_chunk("new content Y", path="y.py"),
            _make_chunk("new content Z", path="z.py"),
        ]

        result = store.reindex_repo(new_chunks, "github", "testorg", "testrepo")
        assert result["deleted"] == 2
        assert result["inserted"] == 3
        assert store.collection_stats()["oracle_code"] == 3

    def test_search_with_source_filter(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        github_chunk = _make_chunk("github code", source="github")
        slack_chunk = _make_chunk("slack message", source="slack")
        store.upsert_chunks([github_chunk, slack_chunk])

        github_results = store.search("code", source="github", min_score=0.0)
        assert all(r["metadata"]["source"] == "github" for r in github_results)

    def test_search_min_score_filter(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        chunks = [_make_chunk("test content")]
        store.upsert_chunks(chunks)

        # min_score filters low-scoring results
        all_results = store.search("unrelated query", min_score=0.0)
        assert len(all_results) == 1
        # Using min_score=1.0 (impossible to achieve) should filter everything
        filtered_results = store.search("unrelated query", min_score=1.0001)
        assert len(filtered_results) == 0

    def test_search_returns_ranked_results(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        chunks = [
            _make_chunk(f"content {i}", path=f"file{i}.py")
            for i in range(5)
        ]
        store.upsert_chunks(chunks)

        results = store.search("content", min_score=0.0)
        # Results should be sorted by score descending
        scores = [r["score"] for r in results]
        assert scores == sorted(scores, reverse=True)

    def test_collection_stats(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        stats = store.collection_stats()
        assert stats == {"oracle_code": 0, "oracle_jira": 0, "oracle_slack": 0, "oracle_web": 0, "oracle_pages": 0}

        store.upsert_chunks([_make_chunk("test")])
        stats = store.collection_stats()
        assert stats["oracle_code"] == 1

    def test_unknown_source_raises(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        with pytest.raises(ValueError, match="Unknown source"):
            store.search("query", source="unknown")

    def test_search_empty_collection(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        results = store.search("anything", min_score=0.0)
        assert results == []


class TestSearchByMetadata:
    def test_search_by_author(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        chunks = [
            _make_chunk("code by alice", author="alice", path="a.py"),
            _make_chunk("code by bob", author="bob", path="b.py"),
            _make_chunk("more code by alice", author="alice", path="c.py"),
        ]
        store.upsert_chunks(chunks)

        results = store.search_by_metadata("author", "alice")
        assert len(results) == 2
        assert all(r["metadata"]["author"] == "alice" for r in results)

    def test_search_by_jira_issue_id(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        chunks = [
            _make_chunk(
                "jira issue content",
                source="jira",
                jira_issue_id="AAP-1234",
                path="AAP-1234",
            ),
            _make_chunk(
                "another issue",
                source="jira",
                jira_issue_id="AAP-5678",
                path="AAP-5678",
            ),
        ]
        store.upsert_chunks(chunks)

        results = store.search_by_metadata("jira_issue_id", "AAP-1234")
        assert len(results) == 1
        assert results[0]["metadata"]["jira_issue_id"] == "AAP-1234"

    def test_search_by_metadata_empty_value(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        results = store.search_by_metadata("author", "")
        assert results == []

    def test_search_by_metadata_with_source_filter(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        chunks = [
            _make_chunk("github code by alice", source="github", author="alice", path="a.py"),
            _make_chunk("slack msg by alice", source="slack", author="alice", path="msg1"),
        ]
        store.upsert_chunks(chunks)

        results = store.search_by_metadata("author", "alice", source="github")
        assert len(results) == 1
        assert results[0]["metadata"]["source"] == "github"

    def test_search_by_metadata_top_k(self, tmp_path: Path) -> None:
        config = OracleConfig()
        store = VectorStore(config, embedder=FakeEmbedder(), db_path=tmp_path / "chroma")

        chunks = [
            _make_chunk(f"chunk {i} by alice", author="alice", path=f"f{i}.py")
            for i in range(10)
        ]
        store.upsert_chunks(chunks)

        results = store.search_by_metadata("author", "alice", top_k=3)
        assert len(results) == 3
