import asyncio

import pytest

from app.models.embedding_record import EmbeddingRecord
from app.models.embedding_record_upsert import EmbeddingRecordUpsert
from app.models.enums import EmbedStatus
from app.services import record_embedding_service


def _item(record_id: str = "emb_chk_1") -> EmbeddingRecordUpsert:
    record = EmbeddingRecord.from_doc_chunk(
        record_id=record_id,
        repo_id="AD/example-repo",
        snapshot_id="snap_abc123",
        commit_sha="a1b2c3d4e5f6",
        text="Overview text for embedding",
        doc_path="docs/architecture.md",
        section_title="Overview",
        graph_node_id=f"doc_chunk_{record_id}",
    )
    return EmbeddingRecordUpsert(record=record)


def test_embed_and_index_records_empty():
    result = asyncio.run(record_embedding_service.embed_and_index_records([]))
    assert result["requested"] == 0
    assert result["embedded"] == 0


def test_embed_and_index_records_success(monkeypatch):
    status_updates: list[tuple[str, EmbedStatus]] = []

    async def _embed_texts(texts):
        return [[0.1] * 1536 for _ in texts]

    class _Service:
        embed_texts = staticmethod(_embed_texts)

    async def _update_status(record_id, status, **kwargs):
        status_updates.append((record_id, status))
        return True

    async def _upsert_record_vectors(items):
        return {"requested": len(items), "upserted": len(items), "mongo_updated": len(items)}

    monkeypatch.setattr(record_embedding_service, "get_embedding_service", lambda: _Service())
    monkeypatch.setattr(
        record_embedding_service.embedding_record_repo,
        "update_embed_status",
        _update_status,
    )
    monkeypatch.setattr(
        record_embedding_service,
        "upsert_record_vectors",
        _upsert_record_vectors,
    )

    result = asyncio.run(record_embedding_service.embed_and_index_records([_item()]))

    assert result["embedded"] == 1
    assert result["indexed"] == 1
    assert ( "emb_chk_1", EmbedStatus.PROCESSING) in status_updates


def test_embed_and_index_records_flushes_incrementally(monkeypatch):
    # A large snapshot must not accumulate every vector in memory for one final
    # upsert; vectors are flushed to Qdrant in chunks as they are embedded.
    async def _embed_texts(texts):
        return [[0.1] * 1536 for _ in texts]

    class _Service:
        embed_texts = staticmethod(_embed_texts)

    async def _update_status(record_id, status, **kwargs):
        return True

    upsert_calls: list[int] = []

    async def _upsert_record_vectors(items):
        upsert_calls.append(len(items))
        return {"requested": len(items), "upserted": len(items), "mongo_updated": len(items)}

    monkeypatch.setattr(record_embedding_service.settings, "EMBEDDING_BATCH_SIZE", 5)
    monkeypatch.setattr(record_embedding_service.settings, "EMBEDDING_UPSERT_FLUSH_SIZE", 10)
    monkeypatch.setattr(record_embedding_service, "get_embedding_service", lambda: _Service())
    monkeypatch.setattr(
        record_embedding_service.embedding_record_repo, "update_embed_status", _update_status
    )
    monkeypatch.setattr(
        record_embedding_service, "upsert_record_vectors", _upsert_record_vectors
    )

    items = [_item(f"emb_{i}") for i in range(25)]
    result = asyncio.run(record_embedding_service.embed_and_index_records(items))

    assert result["embedded"] == 25
    assert result["indexed"] == 25
    assert result["upserted"] == 25
    # 25 records flushing at >=10 -> upserts of 10, 10, 5 (not a single final call)
    assert len(upsert_calls) >= 2
    assert sum(upsert_calls) == 25


def test_embed_and_index_records_marks_failed_on_provider_error(monkeypatch):
    async def _embed_texts(_texts):
        raise RuntimeError("openai unavailable")

    class _Service:
        embed_texts = staticmethod(_embed_texts)

    failed_updates: list[str] = []

    async def _update_status(record_id, status, **kwargs):
        if status == EmbedStatus.FAILED:
            failed_updates.append(record_id)
        return True

    monkeypatch.setattr(record_embedding_service, "get_embedding_service", lambda: _Service())
    monkeypatch.setattr(
        record_embedding_service.embedding_record_repo,
        "update_embed_status",
        _update_status,
    )

    with pytest.raises(RuntimeError, match="openai unavailable"):
        asyncio.run(record_embedding_service.embed_and_index_records([_item("emb_failed")]))

    assert failed_updates == ["emb_failed"]
