from datetime import UTC, datetime
from pathlib import Path

from home_agent.contracts.types import (
    EmbeddingRecord,
    Item,
    ItemKind,
    UrgencyDecision,
    UrgencyLabel,
)
from home_agent.retrieval.backfill import run_embeddings_backfill
from home_agent.retrieval.text_renderer import DefaultItemTextRenderer
from home_agent.storage.sqlite_store import SqliteStore


class StubEmbeddingProvider:
    @property
    def provider_name(self) -> str:
        return "local"

    @property
    def model_name(self) -> str:
        return "stub-v1"

    def embed_texts(self, texts: list[str]) -> list[list[float]]:
        return [[float(index), float(index) + 0.5] for index, _text in enumerate(texts, start=1)]


class ShortEmbeddingProvider:
    @property
    def provider_name(self) -> str:
        return "local"

    @property
    def model_name(self) -> str:
        return "short-v1"

    def embed_texts(self, texts: list[str]) -> list[list[float]]:
        _ = texts
        return [[1.0, 1.5]]


def test_backfill_only_processes_items_missing_embeddings(tmp_path: Path) -> None:
    store = SqliteStore(tmp_path / "agent.db")
    _save_item(store, "older")
    _save_item(store, "newer")
    store.save_item_embedding(
        EmbeddingRecord(
            provider="gmail",
            external_id="older",
            embedding_provider="local",
            embedding_model="stub-v1",
            vector=[0.1, 0.2],
            dimension=2,
            created_at=datetime.now(tz=UTC),
        )
    )

    result = run_embeddings_backfill(
        store,
        StubEmbeddingProvider(),
        DefaultItemTextRenderer(),
        batch_size=10,
        limit=None,
        since=None,
        kind=None,
        dry_run=False,
        rebuild=False,
    )

    assert result.processed == 1
    assert store.get_item_embedding("gmail", "newer", "local", "stub-v1") is not None


def test_backfill_dry_run_reports_candidates_without_writing(tmp_path: Path) -> None:
    store = SqliteStore(tmp_path / "agent.db")
    _save_item(store, "only-item")

    result = run_embeddings_backfill(
        store,
        StubEmbeddingProvider(),
        DefaultItemTextRenderer(),
        batch_size=10,
        limit=None,
        since=None,
        kind=None,
        dry_run=True,
        rebuild=False,
    )

    assert result.processed == 0
    assert result.skipped == 1
    assert store.get_item_embedding("gmail", "only-item", "local", "stub-v1") is None


def test_backfill_marks_whole_batch_failed_when_provider_returns_too_few_vectors(
    tmp_path: Path,
) -> None:
    store = SqliteStore(tmp_path / "agent.db")
    _save_item(store, "first")
    _save_item(store, "second")

    result = run_embeddings_backfill(
        store,
        ShortEmbeddingProvider(),
        DefaultItemTextRenderer(),
        batch_size=10,
        limit=None,
        since=None,
        kind=None,
        dry_run=False,
        rebuild=False,
    )

    assert result.processed == 0
    assert result.failed == 2
    assert store.get_item_embedding("gmail", "first", "local", "short-v1") is None
    assert store.get_item_embedding("gmail", "second", "local", "short-v1") is None


def _save_item(store: SqliteStore, external_id: str) -> None:
    store.save_item(
        Item(
            provider="gmail",
            external_id=external_id,
            kind=ItemKind.EMAIL,
            received_at=datetime.now(tz=UTC),
            subject=f"Subject {external_id}",
            sender="staff@example.com",
            metadata={"snippet": "hello"},
        ),
        UrgencyDecision(
            score=0.5,
            label=UrgencyLabel.MEDIUM,
            reasons=["seed"],
            used_llm=False,
        ),
    )
