from pathlib import Path
from unittest.mock import ANY, MagicMock, patch

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

from bench_af._abstract.objects import Environment, ModelOrganism
from bench_af.cli.validate_model import (
    _parse_eval_log,
    _parse_eval_logs,
    validate_model_organism,
)


class TestValidateModelOrganism:
    """Test validate_model_organism function."""

    def test_validate_model_organism_success_raw_logs(self):
        """Test successful validation returning raw eval logs."""
        # Setup mocks
        mock_model = MagicMock(spec=Model)
        mock_solver = MagicMock(spec=Solver)
        mock_environment = MagicMock(spec=Environment)
        mock_environment.name = "test_task"
        mock_environment.get_observed_task.return_value = MagicMock(spec=Task)
        mock_environment.get_unobserved_task.return_value = MagicMock(
            spec=Task)
        mock_results_dir = MagicMock(spec=Path)
        mock_tools = [MagicMock()]
        mock_environment.get_tools.return_value = mock_tools

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

        mock_eval_logs = [MagicMock(spec=EvalLog)]

        with patch("cli.validate_model.eval") as mock_eval:
            mock_eval.return_value = mock_eval_logs

            result = validate_model_organism(
                model_organism=model_organism,
                environment=mock_environment,
                raw_eval_logs=True,
                eval_config={"some": "config"},
                results_dir=mock_results_dir,
            )

            assert result == mock_eval_logs
            mock_eval.assert_called_once_with(
                tasks=[
                    mock_environment.get_observed_task.return_value,
                    mock_environment.get_unobserved_task.return_value,
                ],
                model=mock_model,
                # we don't care which tools are in solver
                solver=ANY,
                tags=["validate_model_organism"],
                log_dir=str(mock_results_dir / "logs"),
                some="config",
                # we don't care about log_format eval or json
                log_format=ANY
            )

    def test_validate_model_organism_success_parsed_logs(self):
        """Test successful validation returning parsed logs."""
        # Setup mocks
        mock_model = MagicMock(spec=Model)
        mock_solver = MagicMock(spec=Solver)
        mock_environment = MagicMock(spec=Environment)
        mock_environment.name = "test_task"
        mock_environment.get_observed_task.return_value = MagicMock(spec=Task)
        mock_results_dir = MagicMock(spec=Path)

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

        mock_eval_logs = [MagicMock(spec=EvalLog)]

        with (
            patch("cli.validate_model.eval") as mock_eval,
            patch("cli.validate_model._parse_eval_logs") as mock_parse,
        ):
            mock_eval.return_value = mock_eval_logs
            mock_parse.return_value = "Parsed results"

            result = validate_model_organism(
                model_organism=model_organism,
                environment=mock_environment,
                raw_eval_logs=False,
                eval_config={"some": "config"},
                results_dir=mock_results_dir,
            )

            assert result == "Parsed results"
            mock_eval.assert_called_once()
            mock_parse.assert_called_once_with(mock_eval_logs)

    def test_validate_model_organism_unsupported_task(self):
        """Test validation with unsupported task."""
        mock_model = MagicMock(spec=Model)
        mock_solver = MagicMock(spec=Solver)
        mock_results_dir = MagicMock(spec=Path)

        mock_unsupported_environment = MagicMock(spec=Environment)
        mock_unsupported_environment.name = "unsupported_task"

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

        with pytest.raises(
            ValueError,
            match="Environment unsupported_task not supported by model organism test_model",
        ):
            validate_model_organism(
                model_organism=model_organism,
                environment=mock_unsupported_environment,
                raw_eval_logs=True,
                eval_config={},
                results_dir=mock_results_dir,
            )

    def test_validate_model_organism_default_raw_eval_logs(self):
        """Test that raw_eval_logs defaults to True."""
        mock_model = MagicMock(spec=Model)
        mock_solver = MagicMock(spec=Solver)
        mock_environment = MagicMock(spec=Environment)
        mock_environment.name = "test_task"
        mock_environment.get_observed_task.return_value = MagicMock(spec=Task)
        mock_results_dir = MagicMock(spec=Path)

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

        mock_eval_logs = [MagicMock(spec=EvalLog)]

        with patch("cli.validate_model.eval") as mock_eval:
            mock_eval.return_value = mock_eval_logs

            # Don't specify raw_eval_logs parameter
            result = validate_model_organism(
                model_organism=model_organism,
                environment=mock_environment,
                eval_config={},
                results_dir=mock_results_dir,
            )

            # Should return raw logs (default behavior)
            assert result == mock_eval_logs


class TestParseEvalLog:
    """Test _parse_eval_log function."""

    def test_parse_eval_log_success(self):
        """Test parsing successful eval log."""
        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)}

        result = _parse_eval_log(mock_eval_log)

        assert result == "Accuracy: 0.85"

    def test_parse_eval_log_success_none_results(self):
        """Test parsing successful eval log with None results."""
        mock_eval_log = MagicMock(spec=EvalLog)
        mock_eval_log.status = "success"
        mock_eval_log.results = None

        with pytest.raises(ValueError, match="Eval log results are None"):
            _parse_eval_log(mock_eval_log)

    def test_parse_eval_log_error(self):
        """Test parsing failed eval log."""
        mock_eval_log = MagicMock(spec=EvalLog)
        mock_eval_log.status = "error"
        mock_eval_log.error = "Some error occurred"

        result = _parse_eval_log(mock_eval_log)

        assert result == "Error: Some error occurred"

    def test_parse_eval_log_other_status(self):
        """Test parsing eval log with other status."""
        mock_eval_log = MagicMock(spec=EvalLog)
        mock_eval_log.status = "cancelled"
        mock_eval_log.error = "Task was cancelled"

        result = _parse_eval_log(mock_eval_log)

        assert result == "Error: Task was cancelled"


class TestParseEvalLogs:
    """Test _parse_eval_logs function."""

    def test_parse_eval_logs_single(self):
        """Test parsing single eval log."""
        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.9)}

        result = _parse_eval_logs([mock_eval_log])

        assert result == "Accuracy: 0.9"

    def test_parse_eval_logs_multiple(self):
        """Test parsing multiple eval logs."""
        mock_eval_log1 = MagicMock(spec=EvalLog)
        mock_eval_log1.status = "success"
        mock_eval_log1.results = MagicMock()
        mock_eval_log1.results.metrics = {"accuracy": MagicMock(value=0.85)}

        mock_eval_log2 = MagicMock(spec=EvalLog)
        mock_eval_log2.status = "error"
        mock_eval_log2.error = "Failed validation"

        result = _parse_eval_logs([mock_eval_log1, mock_eval_log2])

        expected = "Accuracy: 0.85\nError: Failed validation"
        assert result == expected

    def test_parse_eval_logs_empty(self):
        """Test parsing empty eval logs list."""
        result = _parse_eval_logs([])

        assert result == ""

    def test_parse_eval_logs_mixed_results(self):
        """Test parsing logs with mixed success/error results."""
        mock_eval_log1 = MagicMock(spec=EvalLog)
        mock_eval_log1.status = "success"
        mock_eval_log1.results = MagicMock()
        mock_eval_log1.results.metrics = {"accuracy": MagicMock(value=0.7)}

        mock_eval_log2 = MagicMock(spec=EvalLog)
        mock_eval_log2.status = "success"
        mock_eval_log2.results = MagicMock()
        mock_eval_log2.results.metrics = {"accuracy": MagicMock(value=0.8)}

        mock_eval_log3 = MagicMock(spec=EvalLog)
        mock_eval_log3.status = "error"
        mock_eval_log3.error = "Network timeout"

        result = _parse_eval_logs(
            [mock_eval_log1, mock_eval_log2, mock_eval_log3])

        expected = "Accuracy: 0.7\nAccuracy: 0.8\nError: Network timeout"
        assert result == expected


class TestValidationIntegration:
    """Integration tests for validation pipeline."""

    def test_validation_pipeline_integration(self):
        """Test complete validation pipeline with real objects."""
        # This test would use actual default implementations if available
        # and test the integration between components
        pass  # TODO: Implement when we have more realistic test data

    def test_validation_with_complex_eval_config(self):
        """Test validation with complex eval configuration."""
        mock_model = MagicMock(spec=Model)
        mock_solver = MagicMock(spec=Solver)
        mock_environment = MagicMock(spec=Environment)
        mock_environment.name = "test_task"
        mock_environment.get_observed_task.return_value = MagicMock(spec=Task)
        mock_environment.get_unobserved_task.return_value = MagicMock(
            spec=Task)

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

        complex_config = {
            "log_level": "debug",
            "max_samples": 100,
            "parallel": True,
            "sandbox": "docker",
        }

        mock_eval_logs = [MagicMock(spec=EvalLog)]
        mock_results_dir = MagicMock(spec=Path)

        with patch("cli.validate_model.eval") as mock_eval:
            mock_eval.return_value = mock_eval_logs

            validate_model_organism(
                model_organism=model_organism,
                environment=mock_environment,
                raw_eval_logs=True,
                eval_config=complex_config,
                results_dir=mock_results_dir,
            )

            # Verify all config parameters were passed through
            mock_eval.assert_called_once_with(
                tasks=[
                    mock_environment.get_observed_task.return_value,
                    mock_environment.get_unobserved_task.return_value,
                ],
                model=mock_model,
                solver=ANY,
                tags=["validate_model_organism"],
                log_dir=str(mock_results_dir / "logs"),
                log_level="debug",
                max_samples=100,
                parallel=True,
                log_format=ANY,
                sandbox="docker",
            )
