from __future__ import annotations

import json
import uuid
from typing import Any

import pytest

from amsdal_ml.ml_retrievers.exceptions import MultipleRecordsError
from amsdal_ml.ml_retrievers.query_deleter import NLQueryDeleter
from tests.agents_tests.test_fakes import FakeModel


def get_base_id() -> int:
    return uuid.uuid4().int % (10**9)


BASE_ID = get_base_id()

EXPECTED_TWO = 2


@pytest.mark.asyncio
async def test_nl_query_deleter_applies_not_deleted_base_filter(async_test_amsdal_manager: Any) -> None:  # noqa: ARG001
    from tests.fixtures.models.author import Author

    llm = FakeModel(async_mode=True, responses={})
    llm.setup()

    deleter = NLQueryDeleter(llm=llm, queryset=Author.objects.all())

    assert deleter.executor.base_filters.get('_metadata__is_deleted') is False


@pytest.mark.asyncio
async def test_nl_query_deleter_deletes_single_record(async_test_amsdal_manager: Any) -> None:  # noqa: ARG001
    from tests.fixtures.models.author import Author

    author = Author(id=BASE_ID + 1, name='Alice', email='alice@example.com')
    await author.asave(force_insert=True)

    query = 'delete author Alice'

    split_response = json.dumps({'find_query': 'find author with name Alice'})

    filter_response = json.dumps(
        {
            'filters': [
                {
                    'field': 'name',
                    'lookup': 'eq',
                    'value': 'Alice',
                }
            ]
        }
    )

    llm = FakeModel(async_mode=True, scripted=[split_response, filter_response])
    llm.setup()

    deleter = NLQueryDeleter(llm=llm, queryset=Author.objects.all())
    analysis = await deleter.analyze(query, preview_limit=5)
    docs = await deleter.invoke(analysis)
    assert len(docs) == 1
    deleted = json.loads(docs[0].page_content)
    assert deleted['id'] == str(BASE_ID + 1)

    remaining = await Author.objects.filter(id__eq=BASE_ID + 1).aexecute()
    assert remaining == []


@pytest.mark.asyncio
async def test_nl_query_deleter_no_matches(async_test_amsdal_manager: Any) -> None:  # noqa: ARG001
    from tests.fixtures.models.author import Author

    query = 'delete author Bob'

    split_response = json.dumps({'find_query': 'find author with name Bob'})

    filter_response = json.dumps(
        {
            'filters': [
                {
                    'field': 'name',
                    'lookup': 'eq',
                    'value': 'Bob',
                }
            ]
        }
    )

    llm = FakeModel(async_mode=True, scripted=[split_response, filter_response])
    llm.setup()

    deleter = NLQueryDeleter(llm=llm, queryset=Author.objects.all())

    analysis = await deleter.analyze(query, preview_limit=5)
    with pytest.raises(MultipleRecordsError) as exc:
        await deleter.invoke_one(analysis)

    assert exc.value.total == 0


@pytest.mark.asyncio
async def test_nl_query_deleter_too_many_matches(async_test_amsdal_manager: Any) -> None:  # noqa: ARG001
    from tests.fixtures.models.author import Author

    author1 = Author(id=BASE_ID + 10, name='Alice', email='alice1@example.com')
    author2 = Author(id=BASE_ID + 11, name='Alice', email='alice2@example.com')
    await author1.asave(force_insert=True)
    await author2.asave(force_insert=True)

    query = 'delete author Alice'

    split_response = json.dumps({'find_query': 'find author with name Alice'})

    filter_response = json.dumps(
        {
            'filters': [
                {
                    'field': 'name',
                    'lookup': 'eq',
                    'value': 'Alice',
                }
            ]
        }
    )

    llm = FakeModel(async_mode=True, scripted=[split_response, filter_response])
    llm.setup()

    deleter = NLQueryDeleter(llm=llm, queryset=Author.objects.all())

    analysis = await deleter.analyze(query, preview_limit=5)
    with pytest.raises(MultipleRecordsError) as exc:
        await deleter.invoke_one(analysis)

    assert exc.value.total == EXPECTED_TWO
    assert exc.value.records


@pytest.mark.asyncio
async def test_nl_query_deleter_analyze_and_execute_multiple_records(async_test_amsdal_manager: Any) -> None:  # noqa: ARG001
    from tests.fixtures.models.author import Author

    author1 = Author(id=BASE_ID + 20, name='Alice', email='alice1@example.com')
    author2 = Author(id=BASE_ID + 21, name='Alice', email='alice2@example.com')
    await author1.asave(force_insert=True)
    await author2.asave(force_insert=True)

    query = 'delete author Alice'

    split_response = json.dumps({'find_query': 'find author with name Alice'})

    filter_response = json.dumps(
        {
            'filters': [
                {
                    'field': 'name',
                    'lookup': 'eq',
                    'value': 'Alice',
                }
            ]
        }
    )

    llm = FakeModel(async_mode=True, scripted=[split_response, filter_response])
    llm.setup()

    deleter = NLQueryDeleter(llm=llm, queryset=Author.objects.all())

    preview = await deleter.analyze(query, preview_limit=1)
    assert preview.total == EXPECTED_TWO
    assert len(preview.records) == 1

    deleted_docs = await deleter.invoke(preview)
    assert len(deleted_docs) == EXPECTED_TWO

    remaining = await Author.objects.filter(name__eq='Alice').aexecute()
    assert remaining == []
