"""Unit tests for execution models."""

import pytest
from markitect.prompts.execution.models import (
    RunConfig,
    InputBundle,
    LLMResponse,
    PromptRun,
    ExecutionStage,
    RunStatus,
)


class TestRunConfig:
    """Tests for RunConfig."""

    def test_create_default_config(self):
        """Test creating default config."""
        config = RunConfig()
        assert config.model_name == "gpt-4"
        assert config.temperature == 0.7
        assert config.max_tokens == 2000
        assert config.max_depth == 3
        assert config.skip_if_exists is True

    def test_create_custom_config(self):
        """Test creating custom config."""
        config = RunConfig(
            model_name="gpt-3.5-turbo",
            temperature=0.5,
            max_tokens=1000,
            max_depth=5,
            skip_if_exists=False,
        )
        assert config.model_name == "gpt-3.5-turbo"
        assert config.temperature == 0.5
        assert config.max_tokens == 1000
        assert config.max_depth == 5
        assert config.skip_if_exists is False

    def test_config_to_dict(self):
        """Test serialization."""
        config = RunConfig(model_name="test-model")
        data = config.to_dict()
        assert data["model_name"] == "test-model"
        assert "temperature" in data

    def test_config_from_dict(self):
        """Test deserialization."""
        data = {
            "model_name": "custom-model",
            "temperature": 0.9,
            "max_tokens": 500,
        }
        config = RunConfig.from_dict(data)
        assert config.model_name == "custom-model"
        assert config.temperature == 0.9
        assert config.max_tokens == 500


class TestInputBundle:
    """Tests for InputBundle."""

    def test_create_input_bundle(self):
        """Test creating input bundle."""
        bundle = InputBundle(
            template_digest="abc123",
            dependency_digests={"dep1": "def456", "dep2": "ghi789"},
            resolution_config_hash="config123",
            model_config={"model": "gpt-4", "temp": 0.7},
        )
        assert bundle.template_digest == "abc123"
        assert len(bundle.dependency_digests) == 2

    def test_calculate_hash_deterministic(self):
        """Test hash calculation is deterministic."""
        bundle1 = InputBundle(
            template_digest="abc",
            dependency_digests={"a": "1", "b": "2"},
            resolution_config_hash="conf",
            model_config={"model": "gpt-4"},
        )
        bundle2 = InputBundle(
            template_digest="abc",
            dependency_digests={"b": "2", "a": "1"},  # Different order
            resolution_config_hash="conf",
            model_config={"model": "gpt-4"},
        )
        # Should produce same hash regardless of dict order
        assert bundle1.calculate_hash() == bundle2.calculate_hash()

    def test_calculate_hash_changes_with_content(self):
        """Test hash changes when content changes."""
        bundle1 = InputBundle(
            template_digest="abc",
            dependency_digests={},
            resolution_config_hash="conf",
            model_config={},
        )
        bundle2 = InputBundle(
            template_digest="xyz",  # Different template
            dependency_digests={},
            resolution_config_hash="conf",
            model_config={},
        )
        assert bundle1.calculate_hash() != bundle2.calculate_hash()

    def test_to_dict_includes_hash(self):
        """Test dictionary includes hash."""
        bundle = InputBundle(
            template_digest="abc",
            dependency_digests={},
            resolution_config_hash="conf",
            model_config={},
        )
        data = bundle.to_dict()
        assert "input_bundle_hash" in data
        assert data["input_bundle_hash"] == bundle.calculate_hash()


class TestLLMResponse:
    """Tests for LLMResponse."""

    def test_create_response(self):
        """Test creating LLM response."""
        response = LLMResponse(
            content="Generated text",
            model="gpt-4",
            usage={"total_tokens": 100},
            finish_reason="stop",
        )
        assert response.content == "Generated text"
        assert response.model == "gpt-4"
        assert response.usage["total_tokens"] == 100

    def test_response_to_dict(self):
        """Test serialization."""
        response = LLMResponse(
            content="Text",
            model="gpt-4",
        )
        data = response.to_dict()
        assert data["content"] == "Text"
        assert data["model"] == "gpt-4"


class TestPromptRun:
    """Tests for PromptRun."""

    def test_create_run(self):
        """Test creating run."""
        run = PromptRun.create(
            template_id="template-1",
            input_bundle_hash="hash123",
        )
        assert run.id  # Has UUID
        assert run.template_id == "template-1"
        assert run.input_bundle_hash == "hash123"
        assert run.status == RunStatus.PENDING
        assert run.stage == ExecutionStage.PENDING
        assert run.depth == 0

    def test_create_nested_run(self):
        """Test creating nested run."""
        run = PromptRun.create(
            template_id="template-1",
            input_bundle_hash="hash",
            parent_run_id="parent-123",
            depth=2,
        )
        assert run.parent_run_id == "parent-123"
        assert run.depth == 2

    def test_advance_stage(self):
        """Test advancing execution stage."""
        run = PromptRun.create(
            template_id="template-1",
            input_bundle_hash="hash",
        )
        assert run.stage == ExecutionStage.PENDING

        run.advance_stage(ExecutionStage.ANALYSIS)
        assert run.stage == ExecutionStage.ANALYSIS

        run.advance_stage(ExecutionStage.PROCESSING)
        assert run.stage == ExecutionStage.PROCESSING
        assert run.status == RunStatus.RUNNING

    def test_mark_complete(self):
        """Test marking run as complete."""
        run = PromptRun.create(
            template_id="template-1",
            input_bundle_hash="hash",
        )
        run.mark_complete()

        assert run.status == RunStatus.SUCCESS
        assert run.stage == ExecutionStage.COMPLETE
        assert run.completed_at is not None
        assert run.is_complete()

    def test_mark_failed(self):
        """Test marking run as failed."""
        run = PromptRun.create(
            template_id="template-1",
            input_bundle_hash="hash",
        )
        run.mark_failed("Error message")

        assert run.status == RunStatus.FAILED
        assert run.stage == ExecutionStage.FAILED
        assert run.error_message == "Error message"
        assert run.completed_at is not None
        assert run.is_complete()

    def test_mark_skipped(self):
        """Test marking run as skipped."""
        run = PromptRun.create(
            template_id="template-1",
            input_bundle_hash="hash",
        )
        run.mark_skipped()

        assert run.status == RunStatus.SKIPPED
        assert run.completed_at is not None
        assert run.is_complete()

    def test_run_to_dict(self):
        """Test serialization."""
        run = PromptRun.create(
            template_id="template-1",
            input_bundle_hash="hash",
        )
        data = run.to_dict()

        assert data["id"] == run.id
        assert data["template_id"] == "template-1"
        assert data["input_bundle_hash"] == "hash"
        assert data["status"] == "pending"
        assert data["stage"] == "pending"
