from __future__ import annotations

import json
import uuid
from typing import Any

import pytest

from amsdal_ml.ml_models.utils import ResponseFormat
from amsdal_ml.ml_retrievers.query_deleter import DeleteIntent
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)


async def seed_vehicle(base_id: int) -> tuple[type[Any], list[int]]:
    from tests.fixtures.models.vehicle import Vehicle

    vehicle_one = Vehicle(
        id=base_id + 1,
        make='Toyota',
        model='Camry',
        year=2022,
        engine_type='gasoline',
        fuel_efficiency=30.0,
        features=['airbags'],
        specs={'engine_size': '2.5'},
        is_available=True,
        price=25000.0,
        warranty_years=5,
        maintenance_schedule={'5000': {'oil_change': 'yes'}},
        safety_ratings={'nhtsa': 5},
        options=['navigation'],
    )
    vehicle_two = Vehicle(
        id=base_id + 2,
        make='Tesla',
        model='Model 3',
        year=2023,
        engine_type='electric',
        fuel_efficiency=None,
        features=['autopilot'],
        specs={'battery_range': '358'},
        is_available=False,
        price=45000.0,
        warranty_years=8,
        maintenance_schedule=None,
        safety_ratings={'nhtsa': 5},
        options=['sunroof'],
    )

    await vehicle_one.asave(force_insert=True)
    await vehicle_two.asave(force_insert=True)

    return Vehicle, [vehicle_one.id]


async def seed_catalog_item(base_id: int) -> tuple[type[Any], list[int]]:
    from amsdal_utils.models.data_models.reference import Reference

    from tests.fixtures.models.catalog_item import CatalogItem
    from tests.fixtures.models.category import Category

    category_one = Category(id=base_id + 10, name='Electronics', description='Tech')
    category_two = Category(id=base_id + 11, name='Books', description='Reading')

    await category_one.asave(force_insert=True)
    await category_two.asave(force_insert=True)

    category_one_ref = Reference(
        ref={
            'class_name': 'Category',
            'object_id': category_one.id,
            'resource': Category.__table_name__,
            'class_version': 'LATEST',
            'object_version': 'LATEST',
        }
    )
    category_two_ref = Reference(
        ref={
            'class_name': 'Category',
            'object_id': category_two.id,
            'resource': Category.__table_name__,
            'class_version': 'LATEST',
            'object_version': 'LATEST',
        }
    )

    item_one = CatalogItem(
        id=base_id + 20,
        name='Smartphone',
        price=499.0,
        category=category_one_ref,  # type: ignore[arg-type]
        description='Mobile',
    )
    item_two = CatalogItem(
        id=base_id + 21,
        name='Novel',
        price=19.0,
        category=category_two_ref,  # type: ignore[arg-type]
        description='Fiction',
    )

    await item_one.asave(force_insert=True)
    await item_two.asave(force_insert=True)

    return CatalogItem, [item_one.id]


CASES = [
    (
        'flat',
        seed_vehicle,
        'delete gasoline vehicles',
        json.dumps({'find_query': 'find gasoline vehicles'}),
        json.dumps({'filters': [{'field': 'engine_type', 'lookup': 'eq', 'value': 'gasoline'}]}),
    ),
    (
        'nested',
        seed_catalog_item,
        'delete catalog items in Electronics',
        json.dumps({'find_query': 'find catalog items in Electronics'}),
        json.dumps({'filters': [{'field': 'category__name', 'lookup': 'eq', 'value': 'Electronics'}]}),
    ),
]


@pytest.mark.asyncio
@pytest.mark.parametrize(
    'case_name,seed,query,split_response,filter_response',
    CASES,
    ids=[c[0] for c in CASES],
)
async def test_nl_query_deleter_analyze(
    case_name: str,
    seed: Any,
    query: str,
    split_response: str,
    filter_response: str,
    async_test_amsdal_manager: Any,
) -> None:
    _ = async_test_amsdal_manager
    _ = case_name
    base_id = get_base_id()
    model_class, expected_ids = await seed(base_id)

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

    deleter = NLQueryDeleter(
        llm=llm,
        queryset=model_class.objects.all(),
        llm_response_format=ResponseFormat.JSON_OBJECT,
    )

    analysis = await deleter.analyze(query, preview_limit=5)

    assert analysis.total == len(expected_ids)
    assert analysis.intent == DeleteIntent.DELETE
    assert analysis.target_count == len(expected_ids)
    assert analysis.summary is not None


@pytest.mark.asyncio
@pytest.mark.parametrize(
    'case_name,seed,query,split_response,filter_response',
    CASES,
    ids=[c[0] for c in CASES],
)
async def test_nl_query_deleter_invoke(
    case_name: str,
    seed: Any,
    query: str,
    split_response: str,
    filter_response: str,
    async_test_amsdal_manager: Any,
) -> None:
    _ = async_test_amsdal_manager
    _ = case_name
    base_id = get_base_id()
    model_class, expected_ids = await seed(base_id)

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

    deleter = NLQueryDeleter(
        llm=llm,
        queryset=model_class.objects.all(),
        llm_response_format=ResponseFormat.JSON_OBJECT,
    )

    analysis = await deleter.analyze(query, preview_limit=5)
    docs = await deleter.invoke(analysis)

    assert len(docs) == len(expected_ids)

    for item_id in expected_ids:
        remaining = await model_class.objects.filter(id__eq=item_id).aexecute()
        assert remaining == []


@pytest.mark.asyncio
@pytest.mark.parametrize(
    'case_name,seed,query,split_response,filter_response',
    CASES,
    ids=[c[0] for c in CASES],
)
async def test_nl_query_deleter_invoke_one(
    case_name: str,
    seed: Any,
    query: str,
    split_response: str,
    filter_response: str,
    async_test_amsdal_manager: Any,
) -> None:
    _ = async_test_amsdal_manager
    _ = case_name
    base_id = get_base_id()
    model_class, expected_ids = await seed(base_id)

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

    deleter = NLQueryDeleter(
        llm=llm,
        queryset=model_class.objects.all(),
        llm_response_format=ResponseFormat.JSON_OBJECT,
    )

    doc = await deleter.invoke_one(query)
    parsed = json.loads(doc.page_content)

    assert parsed['id'] == str(expected_ids[0])

    remaining = await model_class.objects.filter(id__eq=expected_ids[0]).aexecute()
    assert remaining == []


@pytest.mark.asyncio
@pytest.mark.parametrize(
    'case_name,seed,query,split_response,filter_response',
    CASES,
    ids=[c[0] for c in CASES],
)
async def test_nl_query_deleter_execute(
    case_name: str,
    seed: Any,
    query: str,
    split_response: str,
    filter_response: str,
    async_test_amsdal_manager: Any,
) -> None:
    _ = async_test_amsdal_manager
    _ = case_name
    base_id = get_base_id()
    model_class, expected_ids = await seed(base_id)

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

    deleter = NLQueryDeleter(
        llm=llm,
        queryset=model_class.objects.all(),
        llm_response_format=ResponseFormat.JSON_OBJECT,
    )

    analysis = await deleter.executor.analyze(query, preview_limit=5)
    results = await deleter.executor.execute(analysis)

    assert len(results) == len(expected_ids)

    for item_id in expected_ids:
        remaining = await model_class.objects.filter(id__eq=item_id).aexecute()
        assert remaining == []


@pytest.mark.asyncio
@pytest.mark.parametrize(
    'case_name,seed,query,split_response,filter_response',
    CASES,
    ids=[c[0] for c in CASES],
)
async def test_nl_query_deleter_execute_one(
    case_name: str,
    seed: Any,
    query: str,
    split_response: str,
    filter_response: str,
    async_test_amsdal_manager: Any,
) -> None:
    _ = async_test_amsdal_manager
    _ = case_name
    base_id = get_base_id()
    model_class, expected_ids = await seed(base_id)

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

    deleter = NLQueryDeleter(
        llm=llm,
        queryset=model_class.objects.all(),
        llm_response_format=ResponseFormat.JSON_OBJECT,
    )

    result = await deleter.executor.execute_one(query)

    assert result.id == expected_ids[0]

    remaining = await model_class.objects.filter(id__eq=expected_ids[0]).aexecute()
    assert remaining == []
