"""Tests for management tools (list_datasets, set_dataset).

Tests cover:
- Tool invoke methods directly
- Edge cases and error conditions
- Backend warning messages
- Derived table info in list_datasets

Note: Tools now return native types (dict) instead of ToolOutput.
"""

from unittest.mock import patch

import pytest

from m4.core.datasets import DatasetDefinition
from m4.core.exceptions import DatasetError
from m4.core.tools.management import (
    ListDatasetsInput,
    ListDatasetsTool,
    SetDatasetInput,
    SetDatasetTool,
)

# All ListDatasetsTool tests disable derived lookups by default.
# Tests that specifically verify derived behavior override this.
_NO_DERIVED = patch("m4.core.tools.management.has_derived_support", return_value=False)


@pytest.fixture
def mock_availability():
    """Mock dataset availability data."""
    return {
        "mimic-iv-demo": {
            "parquet_present": True,
            "db_present": True,
        },
        "mimic-iv": {
            "parquet_present": False,
            "db_present": False,
        },
    }


@pytest.fixture
def dummy_dataset():
    """Create a dummy dataset for passing to invoke (not actually used)."""
    return DatasetDefinition(
        name="dummy",
        modalities=set(),
    )


class TestListDatasetsTool:
    """Test ListDatasetsTool functionality."""

    def test_invoke_lists_available_datasets(self, mock_availability, dummy_dataset):
        """Test that invoke returns dict with dataset info."""
        with (
            _NO_DERIVED,
            patch(
                "m4.core.tools.management.detect_available_local_datasets",
                return_value=mock_availability,
            ),
        ):
            with patch(
                "m4.core.tools.management.get_active_dataset",
                return_value="mimic-iv-demo",
            ):
                with patch("m4.core.tools.management.DatasetRegistry.get") as mock_reg:
                    mock_ds = DatasetDefinition(
                        name="test",
                        bigquery_dataset_ids=["test_ds"],
                    )
                    mock_reg.return_value = mock_ds

                    tool = ListDatasetsTool()
                    result = tool.invoke(dummy_dataset, ListDatasetsInput())

                    # Result is now a dict
                    assert "mimic-iv-demo" in result["datasets"]
                    assert "mimic-iv" in result["datasets"]
                    assert result["selected_dataset"] is None

    def test_invoke_shows_parquet_status(self, mock_availability, dummy_dataset):
        """Test that parquet availability is included."""
        with (
            _NO_DERIVED,
            patch(
                "m4.core.tools.management.detect_available_local_datasets",
                return_value=mock_availability,
            ),
        ):
            with patch(
                "m4.core.tools.management.get_active_dataset",
                return_value="mimic-iv-demo",
            ):
                with patch("m4.core.tools.management.DatasetRegistry.get") as mock_reg:
                    mock_reg.return_value = DatasetDefinition(
                        name="test", bigquery_dataset_ids=[]
                    )

                    tool = ListDatasetsTool()
                    result = tool.invoke(dummy_dataset, ListDatasetsInput())

                    # Demo has parquet, full does not
                    assert (
                        result["datasets"]["mimic-iv-demo"]["parquet_present"] is True
                    )
                    assert result["datasets"]["mimic-iv"]["parquet_present"] is False

    def test_invoke_shows_database_status(self, mock_availability, dummy_dataset):
        """Test that database availability is included."""
        with (
            _NO_DERIVED,
            patch(
                "m4.core.tools.management.detect_available_local_datasets",
                return_value=mock_availability,
            ),
        ):
            with patch(
                "m4.core.tools.management.get_active_dataset",
                return_value="mimic-iv-demo",
            ):
                with patch("m4.core.tools.management.DatasetRegistry.get") as mock_reg:
                    mock_reg.return_value = DatasetDefinition(
                        name="test", bigquery_dataset_ids=[]
                    )

                    tool = ListDatasetsTool()
                    result = tool.invoke(dummy_dataset, ListDatasetsInput())

                    assert result["datasets"]["mimic-iv-demo"]["db_present"] is True
                    assert result["datasets"]["mimic-iv"]["db_present"] is False

    def test_invoke_shows_bigquery_status(self, mock_availability, dummy_dataset):
        """Test that BigQuery support status is included."""
        with (
            _NO_DERIVED,
            patch(
                "m4.core.tools.management.detect_available_local_datasets",
                return_value=mock_availability,
            ),
        ):
            with patch(
                "m4.core.tools.management.get_active_dataset",
                return_value="mimic-iv-demo",
            ):
                with patch("m4.core.tools.management.DatasetRegistry.get") as mock_reg:
                    mock_ds = DatasetDefinition(
                        name="test",
                        bigquery_dataset_ids=["bq_dataset"],  # Has BigQuery
                    )
                    mock_reg.return_value = mock_ds

                    tool = ListDatasetsTool()
                    result = tool.invoke(dummy_dataset, ListDatasetsInput())

                    # Should include bigquery_support field
                    assert "bigquery_support" in result["datasets"]["mimic-iv-demo"]

    def test_invoke_handles_no_datasets(self, dummy_dataset):
        """Test handling when no datasets are available."""
        with (
            _NO_DERIVED,
            patch(
                "m4.core.tools.management.detect_available_local_datasets",
                return_value={},
            ),
        ):
            with patch(
                "m4.core.tools.management.get_active_dataset",
                return_value=None,
            ):
                tool = ListDatasetsTool()
                result = tool.invoke(dummy_dataset, ListDatasetsInput())

                assert result["datasets"] == {}

    def test_invoke_shows_backend_type(self, mock_availability, dummy_dataset):
        """Test that backend type is included."""
        with (
            _NO_DERIVED,
            patch(
                "m4.core.tools.management.detect_available_local_datasets",
                return_value=mock_availability,
            ),
        ):
            with patch(
                "m4.core.tools.management.get_active_dataset",
                return_value="mimic-iv-demo",
            ):
                with patch("m4.core.tools.management.DatasetRegistry.get") as mock_reg:
                    mock_reg.return_value = DatasetDefinition(
                        name="test", bigquery_dataset_ids=[]
                    )
                    with patch.dict("os.environ", {"M4_BACKEND": "duckdb"}):
                        tool = ListDatasetsTool()
                        result = tool.invoke(dummy_dataset, ListDatasetsInput())

                        assert result["backend"] == "duckdb"

    def test_is_compatible_always_true(self):
        """Test that management tools are always compatible."""
        # Empty capabilities dataset
        empty_ds = DatasetDefinition(
            name="empty",
            modalities=set(),
        )

        tool = ListDatasetsTool()
        assert tool.is_compatible(empty_ds) is True

    def test_required_modalities_empty(self):
        """Test that management tool has no required modalities."""
        tool = ListDatasetsTool()
        assert tool.required_modalities == frozenset()


class TestSetDatasetTool:
    """Test deprecated SetDatasetTool behavior."""

    def test_invoke_raises_migration_error(self, dummy_dataset):
        """set_dataset no longer mutates global state."""
        tool = SetDatasetTool()
        params = SetDatasetInput(dataset_name="mimic-iv-demo")

        with pytest.raises(DatasetError) as exc_info:
            tool.invoke(dummy_dataset, params)

        assert "no longer keeps a global active dataset" in str(exc_info.value)
        assert "Pass dataset explicitly" in str(exc_info.value)

    def test_is_compatible_always_true(self):
        """Test that management tools are always compatible."""
        empty_ds = DatasetDefinition(
            name="empty",
            modalities=set(),
        )

        tool = SetDatasetTool()
        assert tool.is_compatible(empty_ds) is True


class TestManagementToolProtocol:
    """Test that management tools conform to the Tool protocol."""

    def test_list_datasets_has_required_attributes(self):
        """Test ListDatasetsTool has all required attributes."""
        tool = ListDatasetsTool()

        assert tool.name == "list_datasets"
        assert (
            "available" in tool.description.lower()
            or "list" in tool.description.lower()
        )
        assert tool.input_model == ListDatasetsInput
        assert isinstance(tool.required_modalities, frozenset)
        assert tool.supported_datasets is None  # Always available

    def test_set_dataset_has_required_attributes(self):
        """Test SetDatasetTool has all required attributes."""
        tool = SetDatasetTool()

        assert tool.name == "set_dataset"
        assert "switch" in tool.description.lower() or "set" in tool.description.lower()
        assert tool.input_model == SetDatasetInput
        assert isinstance(tool.required_modalities, frozenset)
        assert tool.supported_datasets is None  # Always available


class TestListDatasetsDerivedInfo:
    """Test derived table info in list_datasets results."""

    def test_derived_info_for_supported_dataset_duckdb(self, dummy_dataset):
        """Test that derived info is populated for datasets with derived support."""
        availability = {
            "mimic-iv": {
                "parquet_present": True,
                "db_present": True,
                "db_path": "/tmp/mimic_iv.duckdb",
            },
        }

        with (
            patch(
                "m4.core.tools.management.detect_available_local_datasets",
                return_value=availability,
            ),
            patch(
                "m4.core.tools.management.get_active_dataset",
                return_value="mimic-iv",
            ),
            patch(
                "m4.core.tools.management.get_active_backend",
                return_value="duckdb",
            ),
            patch("m4.core.tools.management.DatasetRegistry.get") as mock_reg,
            patch(
                "m4.core.tools.management.has_derived_support",
                return_value=True,
            ),
            patch(
                "m4.core.tools.management.list_builtins",
                return_value=["age", "sofa", "sepsis3"],
            ),
            patch(
                "m4.core.tools.management.get_derived_table_count",
                return_value=2,
            ),
        ):
            mock_reg.return_value = DatasetDefinition(
                name="mimic-iv", bigquery_dataset_ids=["mimiciv_hosp"]
            )

            tool = ListDatasetsTool()
            result = tool.invoke(dummy_dataset, ListDatasetsInput())

            derived = result["datasets"]["mimic-iv"]["derived"]
            assert derived is not None
            assert derived["supported"] is True
            assert derived["total"] == 3
            assert derived["materialized"] == 2

    def test_derived_info_duckdb_no_db(self, dummy_dataset):
        """Test derived info when DuckDB database doesn't exist yet."""
        availability = {
            "mimic-iv": {
                "parquet_present": True,
                "db_present": False,
                "db_path": "",
            },
        }

        with (
            patch(
                "m4.core.tools.management.detect_available_local_datasets",
                return_value=availability,
            ),
            patch(
                "m4.core.tools.management.get_active_dataset",
                return_value="mimic-iv",
            ),
            patch(
                "m4.core.tools.management.get_active_backend",
                return_value="duckdb",
            ),
            patch("m4.core.tools.management.DatasetRegistry.get") as mock_reg,
            patch(
                "m4.core.tools.management.has_derived_support",
                return_value=True,
            ),
            patch(
                "m4.core.tools.management.list_builtins",
                return_value=["age", "sofa"],
            ),
            patch(
                "m4.core.tools.management.get_derived_table_count",
            ) as mock_count,
        ):
            mock_reg.return_value = DatasetDefinition(
                name="mimic-iv", bigquery_dataset_ids=[]
            )

            tool = ListDatasetsTool()
            result = tool.invoke(dummy_dataset, ListDatasetsInput())

            derived = result["datasets"]["mimic-iv"]["derived"]
            assert derived["materialized"] == 0
            mock_count.assert_not_called()

    def test_derived_info_bigquery_backend(self, dummy_dataset):
        """Test derived info on BigQuery (materialized is None)."""
        availability = {
            "mimic-iv": {
                "parquet_present": False,
                "db_present": False,
                "db_path": "",
            },
        }

        with (
            patch(
                "m4.core.tools.management.detect_available_local_datasets",
                return_value=availability,
            ),
            patch(
                "m4.core.tools.management.get_active_dataset",
                return_value="mimic-iv",
            ),
            patch(
                "m4.core.tools.management.get_active_backend",
                return_value="bigquery",
            ),
            patch("m4.core.tools.management.DatasetRegistry.get") as mock_reg,
            patch(
                "m4.core.tools.management.has_derived_support",
                return_value=True,
            ),
            patch(
                "m4.core.tools.management.list_builtins",
                return_value=["age", "sofa", "sepsis3"],
            ),
        ):
            mock_reg.return_value = DatasetDefinition(
                name="mimic-iv", bigquery_dataset_ids=["mimiciv_hosp"]
            )

            tool = ListDatasetsTool()
            result = tool.invoke(dummy_dataset, ListDatasetsInput())

            derived = result["datasets"]["mimic-iv"]["derived"]
            assert derived["supported"] is True
            assert derived["total"] == 3
            assert derived["materialized"] is None

    def test_no_derived_info_for_unsupported_dataset(self, dummy_dataset):
        """Test that derived is None for datasets without derived support."""
        availability = {
            "eicu": {
                "parquet_present": True,
                "db_present": True,
                "db_path": "/tmp/eicu.duckdb",
            },
        }

        with (
            patch(
                "m4.core.tools.management.detect_available_local_datasets",
                return_value=availability,
            ),
            patch(
                "m4.core.tools.management.get_active_dataset",
                return_value="eicu",
            ),
            patch(
                "m4.core.tools.management.get_active_backend",
                return_value="duckdb",
            ),
            patch("m4.core.tools.management.DatasetRegistry.get") as mock_reg,
            patch(
                "m4.core.tools.management.has_derived_support",
                return_value=False,
            ),
        ):
            mock_reg.return_value = DatasetDefinition(
                name="eicu", bigquery_dataset_ids=["eicu_crd"]
            )

            tool = ListDatasetsTool()
            result = tool.invoke(dummy_dataset, ListDatasetsInput())

            assert result["datasets"]["eicu"]["derived"] is None
