from __future__ import annotations

import asyncio
from unittest.mock import AsyncMock

import pytest
from fastapi.testclient import TestClient

from app.api.routes import internal_lucos
from app.models.enums import EmbedStatus, SourceType
from app.models.stored_embedding_record import StoredEmbeddingRecord
from app.qdrant import payload as qdrant_payload


@pytest.fixture(autouse=True)
def _skip_startup_bootstrap(monkeypatch):
    monkeypatch.setattr("app.main.bootstrap_qdrant", lambda *_args, **_kwargs: "adpilot_embeddings")


@pytest.fixture
def client():
    from app.main import app

    return TestClient(app)


def _sample_embed_body() -> dict:
    return {
        "chunks": [
            {
                "chunk_id": "src/auth/middleware.ts:10-42:abc123",
                "chunk_hash": "sha256:deadbeef",
                "repo_id": "lucos:ws:a1b2c3d4e5f67890",
                "file_path": "src/auth/middleware.ts",
                "content": "export function authMiddleware() { return true; }",
                "source_type": "code",
                "start_line": 10,
                "end_line": 42,
                "language": "typescript",
                "symbol_name": "authMiddleware",
                "snapshot_id": "workspace",
                "commit_sha": "abc123",
            }
        ]
    }


def test_lucos_record_payload_includes_required_fields():
    from app.api.routes.internal_lucos import _to_embedding_record
    from app.api.schemas.lucos import LucosChunkEmbedItem

    item = LucosChunkEmbedItem.model_validate(_sample_embed_body()["chunks"][0])
    record = _to_embedding_record(item)
    result = qdrant_payload.embedding_record_to_qdrant_payload(record)

    assert record.record_id == "lucos:lucos:ws:a1b2c3d4e5f67890:src/auth/middleware.ts:10-42:abc123"
    assert result["record_id"] == record.record_id
    assert result["repo_id"] == item.repo_id
    assert result["chunk_hash"] == "sha256:deadbeef"
    assert result["chunk_id"] == item.chunk_id
    assert result["start_line"] == 10
    assert result["end_line"] == 42
    assert result["language"] == "typescript"
    assert result["symbol_name"] == "authMiddleware"
    assert result["source_type"] == SourceType.CODE.value


def test_embed_lucos_chunks_happy_path(client, monkeypatch):
    upsert_mock = AsyncMock(return_value={"requested": 1, "upserted": 1, "modified": 0})
    get_mock = AsyncMock(return_value={})
    embed_mock = AsyncMock(return_value={"embedded": 1, "indexed": 1, "failed": 0})

    monkeypatch.setattr(internal_lucos.embedding_record_repo, "upsert_record_items", upsert_mock)
    monkeypatch.setattr(internal_lucos.embedding_record_repo, "get_records_by_ids", get_mock)
    monkeypatch.setattr(internal_lucos, "embed_and_index_records", embed_mock)

    response = client.post("/internal/lucos/chunks/embed", json=_sample_embed_body())

    assert response.status_code == 200
    body = response.json()
    assert body == {"requested": 1, "embedded": 1, "indexed": 1, "failed": 0}
    upsert_mock.assert_awaited_once()
    embed_mock.assert_awaited_once()


def test_embed_lucos_chunks_skips_unchanged_hash(client, monkeypatch):
    from app.api.routes.internal_lucos import _to_embedding_record
    from app.api.schemas.lucos import LucosChunkEmbedItem

    item = LucosChunkEmbedItem.model_validate(_sample_embed_body()["chunks"][0])
    record = _to_embedding_record(item)
    stored = StoredEmbeddingRecord(
        **record.model_dump(),
        dedupe_key="dedupe",
        embed_status=EmbedStatus.EMBEDDED,
    )

    upsert_mock = AsyncMock()
    get_mock = AsyncMock(return_value={record.record_id: stored})
    embed_mock = AsyncMock()

    monkeypatch.setattr(internal_lucos.embedding_record_repo, "upsert_record_items", upsert_mock)
    monkeypatch.setattr(internal_lucos.embedding_record_repo, "get_records_by_ids", get_mock)
    monkeypatch.setattr(internal_lucos, "embed_and_index_records", embed_mock)

    response = client.post("/internal/lucos/chunks/embed", json=_sample_embed_body())

    assert response.status_code == 200
    assert response.json()["embedded"] == 1
    assert response.json()["indexed"] == 1
    embed_mock.assert_not_called()
    upsert_mock.assert_not_called()


def test_embed_lucos_chunks_rejects_empty_payload(client):
    response = client.post("/internal/lucos/chunks/embed", json={"chunks": []})
    assert response.status_code == 422


def test_embed_lucos_chunks_rejects_invalid_chunks(client, monkeypatch):
    monkeypatch.setattr(
        internal_lucos.embedding_record_repo,
        "get_records_by_ids",
        AsyncMock(return_value={}),
    )
    response = client.post(
        "/internal/lucos/chunks/embed",
        json={
            "chunks": [
                {
                    "chunk_id": "bad",
                    "chunk_hash": "sha256:bad",
                    "repo_id": "lucos:ws:abc",
                    "file_path": "README.md",
                    "content": "   ",
                    "source_type": "docs",
                    "start_line": 1,
                    "end_line": 2,
                    "snapshot_id": "workspace",
                }
            ]
        },
    )
    assert response.status_code == 400


def test_delete_lucos_chunk_vectors(client, monkeypatch):
    delete_mock = AsyncMock(return_value={"deleted": 1, "mongo_updated": 1})
    monkeypatch.setattr(internal_lucos, "delete_record_vectors", delete_mock)

    response = client.post(
        "/internal/lucos/chunks/delete",
        json={"record_ids": ["lucos:lucos:ws:abc:chunk-1"]},
    )

    assert response.status_code == 200
    assert response.json() == {"deleted": 1, "mongo_updated": 1}
    delete_mock.assert_awaited_once()


def test_delete_lucos_chunk_vectors_empty_list(client, monkeypatch):
    delete_mock = AsyncMock()
    monkeypatch.setattr(internal_lucos, "delete_record_vectors", delete_mock)

    response = client.post("/internal/lucos/chunks/delete", json={"record_ids": []})

    assert response.status_code == 200
    assert response.json() == {"deleted": 0, "mongo_updated": 0}
    delete_mock.assert_not_called()


def test_filter_upserts_needing_embedding_skips_matching_hash(monkeypatch):
    from app.models.embedding_record import EmbeddingRecord
    from app.models.embedding_record_upsert import EmbeddingRecordUpsert
    from app.services.embedding_record_builder import filter_upserts_needing_embedding

    record = EmbeddingRecord.from_code_chunk(
        record_id="lucos:lucos:ws:abc:chunk-1",
        repo_id="lucos:ws:abc",
        snapshot_id="workspace",
        commit_sha="abc",
        text="export const x = 1",
        file_path="src/x.ts",
        symbol_name="x",
        start_line=1,
        end_line=1,
        graph_node_id="lucos:chunk-1",
        chunk_hash="sha256:same",
    )
    upsert = EmbeddingRecordUpsert(record=record, upstream_chunk_id="chunk-1")
    stored = StoredEmbeddingRecord(
        **record.model_dump(),
        dedupe_key="dedupe",
        embed_status=EmbedStatus.EMBEDDED,
    )

    get_mock = AsyncMock(return_value={record.record_id: stored})
    monkeypatch.setattr(
        "app.services.embedding_record_builder.embedding_record_repo.get_records_by_ids",
        get_mock,
    )

    pending, skipped = asyncio.run(filter_upserts_needing_embedding([upsert]))

    assert pending == []
    assert skipped == 1


def test_filter_upserts_needing_embedding_does_not_skip_pending(monkeypatch):
    from app.models.embedding_record import EmbeddingRecord
    from app.models.embedding_record_upsert import EmbeddingRecordUpsert
    from app.services.embedding_record_builder import filter_upserts_needing_embedding

    record = EmbeddingRecord.from_code_chunk(
        record_id="lucos:lucos:ws:abc:chunk-1",
        repo_id="lucos:ws:abc",
        snapshot_id="workspace",
        commit_sha="abc",
        text="export const x = 1",
        file_path="src/x.ts",
        symbol_name="x",
        start_line=1,
        end_line=1,
        graph_node_id="lucos:chunk-1",
        chunk_hash="sha256:same",
    )
    upsert = EmbeddingRecordUpsert(record=record, upstream_chunk_id="chunk-1")
    stored = StoredEmbeddingRecord(
        **record.model_dump(),
        dedupe_key="dedupe",
        embed_status=EmbedStatus.PENDING,
    )

    get_mock = AsyncMock(return_value={record.record_id: stored})
    monkeypatch.setattr(
        "app.services.embedding_record_builder.embedding_record_repo.get_records_by_ids",
        get_mock,
    )

    pending, skipped = asyncio.run(filter_upserts_needing_embedding([upsert]))

    assert pending == [upsert]
    assert skipped == 0
