from typing import cast
from unittest.mock import MagicMock

import pytest
from inspect_ai.log import EvalLog
from inspect_ai.model import Model
from inspect_ai.solver import Solver

from bench_af._abstract.objects import Detector, ModelOrganism


class TestModelOrganism:
    """Test ModelOrganism dataclass functionality."""

    def test_model_organism_creation(self):
        """Test basic ModelOrganism creation."""
        mock_model = MagicMock(spec=Model)
        mock_solver = MagicMock(spec=Solver)

        organism = ModelOrganism(
            name="test_model",
            model=mock_model,
            solver=mock_solver,
            supported_environments={"test_task"},
        )

        assert organism.name == "test_model"
        assert organism.model == mock_model
        assert organism.solver == mock_solver
        assert organism.supported_environments == {"test_task"}
        assert organism.inspect_load_name is None

    def test_model_organism_with_inspect_load_name(self):
        """Test ModelOrganism creation with inspect_load_name."""
        mock_model = MagicMock(spec=Model)
        mock_solver = MagicMock(spec=Solver)

        organism = ModelOrganism(
            name="test_model",
            model=mock_model,
            solver=mock_solver,
            supported_environments={"test_task"},
            inspect_load_name="openai/gpt-4o",
        )

        assert organism.inspect_load_name == "openai/gpt-4o"

    def test_model_organism_with_multiple_solvers(self):
        """Test ModelOrganism with list of solvers."""
        mock_model = MagicMock(spec=Model)
        mock_solver1 = MagicMock(spec=Solver)
        mock_solver2 = MagicMock(spec=Solver)

        organism = ModelOrganism(
            name="test_model",
            model=mock_model,
            solver=[mock_solver1, mock_solver2],
            supported_environments={"test_task"},
        )

        assert organism.solver == [mock_solver1, mock_solver2]

    def test_model_organism_with_multiple_tasks(self):
        """Test ModelOrganism with multiple supported tasks."""
        mock_model = MagicMock(spec=Model)
        mock_solver = MagicMock(spec=Solver)

        organism = ModelOrganism(
            name="test_model",
            model=mock_model,
            solver=mock_solver,
            supported_environments={"test_task1", "test_task2"},
        )

        assert len(organism.supported_environments) == 2
        assert "test_task1" in organism.supported_environments
        assert "test_task2" in organism.supported_environments


class MockDetector(Detector):
    """Mock detector implementation for testing."""

    def get_prediction(
        self, *, model_organism: ModelOrganism, environment, eval_logs: list[EvalLog]
    ) -> tuple[float, str]:
        return 0.5, "Mock prediction"


class TestDetector:
    """Test Detector abstract base class functionality."""

    def test_detector_creation(self):
        """Test basic Detector creation."""
        detector = MockDetector(
            name="test_detector",
            supported_model_organisms={"model1", "model2"},
            supported_environments={"test_task"},
        )

        assert detector.name == "test_detector"
        assert detector.supported_model_organisms == {"model1", "model2"}
        assert detector.supported_environments == {"test_task"}

    def test_check_model_supported_success(self):
        """Test _check_model_supported with supported model."""
        detector = MockDetector(
            name="test_detector",
            supported_model_organisms={"test_model"},
            supported_environments={"test_task"},
        )

        mock_organism = MagicMock(spec=ModelOrganism)
        mock_organism.name = "test_model"

        # Should not raise an exception
        detector._check_model_supported(mock_organism)

    def test_check_model_supported_failure(self):
        """Test _check_model_supported with unsupported model."""
        detector = MockDetector(
            name="test_detector",
            supported_model_organisms={"other_model"},
            supported_environments={"test_task"},
        )

        mock_organism = MagicMock(spec=ModelOrganism)
        mock_organism.name = "unsupported_model"

        with pytest.raises(
            ValueError,
            match="Model organism unsupported_model not supported by detector test_detector",
        ):
            detector._check_model_supported(mock_organism)

    def test_check_environment_supported_success(self):
        """Test _check_environment_supported with supported environment."""
        from bench_af._abstract.objects import Environment

        mock_environment = MagicMock(spec=Environment)
        mock_environment.name = "test_task"

        detector = MockDetector(
            name="test_detector",
            supported_model_organisms={"model1"},
            supported_environments={"test_task"},
        )

        # Should not raise an exception
        detector._check_environment_supported(mock_environment)

    def test_check_environment_supported_failure(self):
        """Test _check_environment_supported with unsupported environment."""
        from bench_af._abstract.objects import Environment

        mock_environment1 = MagicMock(spec=Environment)
        mock_environment1.name = "supported_task"
        mock_environment2 = MagicMock(spec=Environment)
        mock_environment2.name = "unsupported_task"

        detector = MockDetector(
            name="test_detector",
            supported_model_organisms={"model1"},
            supported_environments={"test_task1"},
        )

        with pytest.raises(
            ValueError,
            match="Environment unsupported_task not supported by detector test_detector",
        ):
            detector._check_environment_supported(mock_environment2)

    def test_check_model_and_environment_supported_success(self):
        """Test check_model_and_environment_supported with both supported."""
        from bench_af._abstract.objects import Environment

        mock_environment = MagicMock(spec=Environment)
        mock_environment.name = "test_task"

        detector = MockDetector(
            name="test_detector",
            supported_model_organisms={"test_model"},
            supported_environments={"test_task"},
        )

        mock_organism = MagicMock(spec=ModelOrganism)
        mock_organism.name = "test_model"

        # Should not raise an exception
        detector.check_model_and_environment_supported(
            model_organism=mock_organism, environment=mock_environment
        )

    def test_check_model_and_environment_supported_model_failure(self):
        """Test check_model_and_environment_supported with unsupported model."""
        from bench_af._abstract.objects import Environment

        mock_environment = MagicMock(spec=Environment)
        mock_environment.name = "test_task"

        detector = MockDetector(
            name="test_detector",
            supported_model_organisms={"other_model"},
            supported_environments={"test_task"},
        )

        mock_organism = MagicMock(spec=ModelOrganism)
        mock_organism.name = "unsupported_model"

        with pytest.raises(
            ValueError, match="Model organism unsupported_model not supported"
        ):
            detector.check_model_and_environment_supported(
                model_organism=mock_organism, environment=mock_environment
            )

    def test_check_model_and_environment_supported_environment_failure(self):
        """Test check_model_and_environment_supported with unsupported environment."""
        from bench_af._abstract.objects import Environment

        mock_environment1 = MagicMock(spec=Environment)
        mock_environment1.name = "supported_task"
        mock_environment2 = MagicMock(spec=Environment)
        mock_environment2.name = "unsupported_task"

        detector = MockDetector(
            name="test_detector",
            supported_model_organisms={"test_model"},
            supported_environments={"test_task1"},
        )

        mock_organism = MagicMock(spec=ModelOrganism)
        mock_organism.name = "test_model"

        with pytest.raises(
            ValueError, match="Environment unsupported_task not supported"
        ):
            detector.check_model_and_environment_supported(
                model_organism=mock_organism, environment=mock_environment2
            )

    def test_get_new_model_and_environment_default(self):
        """Test get_new_model_and_environment default implementation."""
        from bench_af._abstract.objects import Environment

        mock_environment = MagicMock(spec=Environment)
        detector = MockDetector(
            name="test_detector",
            supported_model_organisms={"test_model"},
            supported_environments={"test_task"},
        )

        mock_organism = MagicMock(spec=ModelOrganism)

        result_organism, result_environment = detector.get_new_model_and_environment(
            original_model_organism=mock_organism, original_environment=mock_environment
        )

        # Default implementation should return the same objects
        assert result_organism is mock_organism
        assert result_environment is mock_environment

    def test_get_prediction_abstract_method(self):
        """Test that get_prediction is implemented in concrete class."""
        from bench_af._abstract.objects import Environment

        mock_environment = MagicMock(spec=Environment)
        mock_organism = MagicMock(spec=ModelOrganism)
        # Create proper EvalLog mocks following the pattern from conftest.py
        mock_eval_log = MagicMock(spec=EvalLog)
        mock_eval_log.status = "success"
        mock_eval_log.results = MagicMock()
        mock_eval_log.results.metrics = {"accuracy": MagicMock(value=0.85)}
        mock_eval_logs = cast("list[EvalLog]", [mock_eval_log])

        detector = MockDetector(
            name="test_detector",
            supported_model_organisms={"test_model"},
            supported_environments={"test_task"},
        )

        probability, explanation = detector.get_prediction(
            model_organism=mock_organism,
            environment=mock_environment,
            eval_logs=mock_eval_logs,
        )

        assert probability == 0.5
        assert explanation == "Mock prediction"
