from __future__ import annotations

import json
from unittest.mock import AsyncMock
from unittest.mock import patch

import pytest
from amsdal_utils.models.enums import ModuleType
from amsdal_utils.models.enums import TransactionType

from amsdal_ml.mcp_server.server_model_explorer.services.types import TransactionScope
from amsdal_ml.mcp_server.server_model_explorer.tools.discovery import get_list_models
from amsdal_ml.mcp_server.server_model_explorer.tools.discovery import get_model_relationships
from amsdal_ml.mcp_server.server_model_explorer.tools.discovery import get_model_schema
from amsdal_ml.mcp_server.server_model_explorer.tools.discovery import list_available_transactions


@pytest.mark.asyncio
async def test_get_list_models(mock_registry):  # noqa: ARG001
    result_json = await get_list_models()
    result = json.loads(result_json)
    assert len(result) == 1
    assert result[0]['model_name'] == 'User'
    assert 'name' in result[0]['fields']
    assert 'age' in result[0]['fields']
    assert result[0]['relationships'] == []


@pytest.mark.asyncio
async def test_get_list_models_with_relationships(mock_registry_with_relationships):  # noqa: ARG001
    result_json = await get_list_models()
    result = json.loads(result_json)
    models_by_name = {m['model_name']: m for m in result}

    assert models_by_name['CatalogItem']['relationships'] == ['category -> Category']
    assert models_by_name['Category']['relationships'] == []
    assert models_by_name['Book']['relationships'] == []


@pytest.mark.asyncio
async def test_get_list_models_scope_user(mock_registry_with_relationships):  # noqa: ARG001
    result_json = await get_list_models(scope=ModuleType.USER)
    result = json.loads(result_json)
    model_names = {entry['model_name'] for entry in result}

    assert 'Category' in model_names
    assert 'CatalogItem' in model_names
    assert 'Book' in model_names
    assert 'Product' not in model_names


@pytest.mark.asyncio
async def test_get_list_models_scope_contrib(mock_registry_with_relationships):  # noqa: ARG001
    result_json = await get_list_models(scope=ModuleType.CONTRIB)
    result = json.loads(result_json)
    assert [entry['model_name'] for entry in result] == ['Product']


@pytest.mark.asyncio
async def test_get_model_schema_found(mock_registry):  # noqa: ARG001
    result = await get_model_schema('User')
    assert isinstance(result, dict)
    assert result['model'] == 'User'
    assert 'schema' in result


@pytest.mark.asyncio
async def test_get_model_schema_not_found(mock_registry):  # noqa: ARG001
    result = await get_model_schema('NonExistentModel')
    assert isinstance(result, dict)
    assert 'error' in result


@pytest.mark.asyncio
async def test_get_model_relationships_found(mock_registry_with_relationships):  # noqa: ARG001
    result = await get_model_relationships('CatalogItem')
    assert result['model'] == 'CatalogItem'
    assert len(result['relationships']) == 1
    rel = result['relationships'][0]
    assert rel['field'] == 'category'
    assert rel['references_model'] == 'Category'
    assert rel['type'] == 'many_to_one'
    assert rel['required'] is True
    assert result['referenced_by'] == []


@pytest.mark.asyncio
async def test_get_model_relationships_not_found(mock_registry_with_relationships):  # noqa: ARG001
    result = await get_model_relationships('NonExistentModel')
    assert 'error' in result


@pytest.mark.asyncio
async def test_get_model_relationships_junction_table(mock_registry_with_relationships):  # noqa: ARG001
    result = await get_model_relationships('Book')
    assert result['model'] == 'Book'
    assert result['relationships'] == []
    assert result['referenced_by'] == []


@pytest.mark.asyncio
async def test_get_model_relationships_no_relationships(mock_registry_with_relationships):  # noqa: ARG001
    result = await get_model_relationships('Category')
    assert result['model'] == 'Category'
    assert result['relationships'] == []
    assert len(result['referenced_by']) == 1
    assert result['referenced_by'][0]['model'] == 'CatalogItem'


@pytest.mark.asyncio
@pytest.mark.parametrize('scope', [TransactionScope.ALL, TransactionScope.USER, TransactionScope.CONTRIB])
async def test_list_available_transactions_uses_catalog(scope: TransactionScope):
    with patch(
        'amsdal_ml.mcp_server.server_model_explorer.tools.discovery.collect_transaction_catalog',
        new_callable=AsyncMock,
    ) as mock_catalog:
        mock_catalog.return_value = {
            'AddToCart': {
                'name': 'AddToCart',
                'description': '',
                'scope': TransactionType.USER,
                'parameters': [
                    {
                        'name': 'quantity',
                        'annotation': 'int',
                        'kind': 'primitive',
                        'model_name': None,
                    }
                ],
            }
        }

        result = await list_available_transactions(scope=scope)

        mock_catalog.assert_awaited_once_with(scope=scope)
        assert isinstance(result, dict)
        assert 'transactions' in result
        assert len(result['transactions']) == 1
        assert result['transactions'][0]['name'] == 'AddToCart'
