from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
from unittest.mock import patch

import pytest

from amsdal_ml.mcp_server.server_model_explorer.services.types import CrudOperation
from amsdal_ml.mcp_server.server_model_explorer.tools.crud import _run_crud_confirm_flow
from amsdal_ml.mcp_server.server_model_explorer.tools.crud import perform_crud_operation
from tests.fixtures.models.user import User


class _FakeAnalysisModel:
    @staticmethod
    def model_validate(payload):
        return payload


@pytest.mark.asyncio
async def test_perform_crud_operation_read(mock_registry, mock_llm):  # noqa: ARG001
    mock_queryset = MagicMock()
    mock_queryset.all.return_value = mock_queryset

    with (
        patch.object(User, 'objects', mock_queryset),
        patch('amsdal_ml.mcp_server.server_model_explorer.tools.crud.NLQueryExecutor') as mock_executor,
    ):
        executor_instance = AsyncMock()
        mock_executor.return_value = executor_instance

        mock_record = User(id=1, name='Test', email='t@test.dev', age=25)
        executor_instance.execute.return_value = [mock_record]

        with patch(
            'amsdal_ml.mcp_server.server_model_explorer.tools.crud.serialize_and_clean_record',
            new_callable=AsyncMock,
        ) as mock_serialize:
            mock_serialize.return_value = {'name': 'Test', 'age': 25}

            result = await perform_crud_operation(
                operation=CrudOperation.READ,
                model_name='User',
                query='find users over 20',
                confirm=True,
                analysis={
                    'query': 'find users over 20',
                    'filters': {},
                    'total': 1,
                    'records': [{'name': 'Test', 'age': 25}],
                },
            )

            assert isinstance(result, dict)
            assert result['operation'] == 'read'
            assert result['model'] == 'User'
            assert len(result['data']) == 1
            assert result['data'][0]['name'] == 'Test'


@pytest.mark.asyncio
async def test_perform_crud_operation_model_not_found(mock_registry):  # noqa: ARG001
    result = await perform_crud_operation(operation=CrudOperation.READ, model_name='NonExistentModel', query='query')
    assert isinstance(result, str)
    assert "Error: Model 'NonExistentModel' not found" in result


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ('operation', 'executor_path', 'analysis_type_name'),
    [
        (CrudOperation.CREATE, 'NLQueryCreatorExecutor', 'CreateAnalysis'),
        (CrudOperation.UPDATE, 'NLQueryUpdaterExecutor', 'UpdateAnalysis'),
        (CrudOperation.DELETE, 'NLQueryDeleterExecutor', 'DeleteAnalysis'),
    ],
)
async def test_perform_crud_operation_non_read_branches(
    mock_registry,
    mock_llm,
    operation: CrudOperation,
    executor_path: str,
    analysis_type_name: str,
):
    _ = (mock_registry, mock_llm)
    with (
        patch(f'amsdal_ml.mcp_server.server_model_explorer.tools.crud.{executor_path}') as mock_executor,
        patch(
            'amsdal_ml.mcp_server.server_model_explorer.tools.crud._run_crud_confirm_flow',
            new_callable=AsyncMock,
        ) as mock_flow,
    ):
        executor_instance = MagicMock()
        mock_executor.return_value = executor_instance
        mock_flow.return_value = {
            'operation': operation.value,
            'model': 'User',
            'analysis': {},
            'requires_confirmation': True,
        }

        result = await perform_crud_operation(
            operation=operation,
            model_name='User',
            query='do something',
            confirm=False,
        )

        assert isinstance(result, dict)
        assert result['operation'] == operation.value
        assert result['model'] == 'User'

        assert mock_flow.await_args is not None
        call_kwargs = mock_flow.await_args.kwargs
        assert call_kwargs['operation'] == operation
        assert call_kwargs['model_name'] == 'User'
        assert call_kwargs['executor'] is executor_instance
        assert call_kwargs['analysis_model'].__name__ == analysis_type_name


@pytest.mark.asyncio
async def test_run_crud_confirm_flow_preview_mode():
    executor = SimpleNamespace(
        analyze=AsyncMock(return_value=SimpleNamespace(model_dump=lambda: {'filters': {}, 'total': 1}))
    )

    result = await _run_crud_confirm_flow(
        operation=CrudOperation.READ,
        model_name='User',
        executor=executor,
        analysis_model=_FakeAnalysisModel,
        query='find user',
        confirm=False,
        analysis_payload=None,
        analyze_kwargs={'query': 'find user'},
    )

    assert result['operation'] == 'read'
    assert result['model'] == 'User'


@pytest.mark.asyncio
async def test_run_crud_confirm_flow_confirm_mode_with_payload():
    fake_model = SimpleNamespace(object_id='obj-1')
    executor = SimpleNamespace(
        analyze=AsyncMock(),
        execute=AsyncMock(return_value=[fake_model]),
    )

    with patch(
        'amsdal_ml.mcp_server.server_model_explorer.tools.crud.serialize_and_clean_record',
        new_callable=AsyncMock,
    ) as mock_serialize:
        mock_serialize.return_value = {'object_id': 'obj-1'}

        result = await _run_crud_confirm_flow(
            operation=CrudOperation.UPDATE,
            model_name='User',
            executor=executor,
            analysis_model=_FakeAnalysisModel,
            query='update user',
            confirm=True,
            analysis_payload={'raw': 'analysis'},
            analyze_kwargs={'query': 'update user'},
        )

    assert result['operation'] == 'update'
    assert result['model'] == 'User'


@pytest.mark.asyncio
async def test_perform_crud_operation_authorization_error(mock_registry, mock_llm):
    _ = (mock_registry, mock_llm)
    with patch(
        'amsdal_ml.mcp_server.server_model_explorer.tools.crud.authorize_class',
        new_callable=AsyncMock,
        side_effect=RuntimeError('forbidden'),
    ):
        result = await perform_crud_operation(
            operation=CrudOperation.READ,
            model_name='User',
            query='find',
        )

    assert isinstance(result, str)
    assert result.startswith('Authorization error:')


@pytest.mark.asyncio
@pytest.mark.parametrize(
    ('operation', 'executor_path'),
    [
        (CrudOperation.READ, 'NLQueryExecutor'),
        (CrudOperation.CREATE, 'NLQueryCreatorExecutor'),
        (CrudOperation.UPDATE, 'NLQueryUpdaterExecutor'),
        (CrudOperation.DELETE, 'NLQueryDeleterExecutor'),
    ],
)
async def test_perform_crud_operation_branch_runtime_error(
    mock_registry,
    mock_llm,
    operation: CrudOperation,
    executor_path: str,
):
    _ = (mock_registry, mock_llm)
    with patch(
        f'amsdal_ml.mcp_server.server_model_explorer.tools.crud.{executor_path}',
        side_effect=RuntimeError('boom'),
    ):
        result = await perform_crud_operation(
            operation=operation,
            model_name='User',
            query='any',
        )

    assert isinstance(result, str)
    assert result.startswith('Error executing query:')
