#!/usr/bin/env python3

"""Shared fixtures for writing steps domain service tests."""

from pathlib import Path
from unittest.mock import AsyncMock, Mock

import pytest

from noveler.domain.interfaces.logger_service_protocol import ILoggerService
from noveler.domain.interfaces.path_service_protocol import IPathService
from noveler.domain.interfaces.progressive_write_llm_executor import (
    LLMExecutionRequest,
    LLMExecutionResult,
)
from noveler.domain.value_objects.scene_data import SceneData


@pytest.fixture
def mock_logger_service() -> Mock:
    """Create a mock logger service."""
    mock = Mock(spec=ILoggerService)
    mock.info = Mock()
    mock.warning = Mock()
    mock.error = Mock()
    mock.debug = Mock()
    return mock


@pytest.fixture
def mock_path_service() -> Mock:
    """Create a mock path service."""
    mock = Mock(spec=IPathService)
    mock.get_project_root = Mock(return_value=Path("/tmp/project"))
    mock.get_episode_file = Mock(return_value=Path("/tmp/project/50_episode.md"))
    return mock


class MockLLMExecutorFactory:
    """Factory for creating various mock LLM executors."""

    @staticmethod
    def success_executor() -> Mock:
        """Create an executor that always succeeds."""
        mock = Mock()
        mock.run = AsyncMock(
            return_value=LLMExecutionResult(
                success=True,
                response_content="生成されたテキスト。" * 150,
                extracted_data={},
                metadata={"tokens_used": 1500},
            )
        )
        mock.call_count = 0
        return mock

    @staticmethod
    def failure_executor() -> Mock:
        """Create an executor that always fails."""
        mock = Mock()
        mock.run = AsyncMock(
            return_value=LLMExecutionResult(
                success=False,
                response_content="",
                error_message="LLM execution failed",
                extracted_data={},
                metadata={},
            )
        )
        return mock

    @staticmethod
    def short_output_executor() -> Mock:
        """Create an executor that returns too-short output."""
        mock = Mock()
        mock.run = AsyncMock(
            return_value=LLMExecutionResult(
                success=True,
                response_content="短い",
                extracted_data={},
                metadata={},
            )
        )
        return mock

    @staticmethod
    def placeholder_executor() -> Mock:
        """Create an executor that returns content with placeholders."""
        mock = Mock()
        mock.run = AsyncMock(
            return_value=LLMExecutionResult(
                success=True,
                response_content="テキスト【ここに執筆】" * 50,
                extracted_data={},
                metadata={},
            )
        )
        return mock

    @staticmethod
    def eventually_succeeds_executor(fail_count: int = 1) -> Mock:
        """Create an executor that fails then succeeds."""
        async def side_effect(request):
            side_effect.call_count += 1
            if side_effect.call_count <= fail_count:
                return LLMExecutionResult(
                    success=False,
                    response_content="",
                    error_message="Simulated failure",
                    extracted_data={},
                    metadata={},
                )
            return LLMExecutionResult(
                success=True,
                response_content="生成されたテキスト。" * 150,
                extracted_data={},
                metadata={},
            )

        side_effect.call_count = 0
        mock = Mock()
        mock.run = AsyncMock(side_effect=side_effect)
        return mock


@pytest.fixture
def llm_executor_factory() -> MockLLMExecutorFactory:
    """Provide the mock executor factory."""
    return MockLLMExecutorFactory()


@pytest.fixture
def sample_scene_data_simple() -> SceneData:
    """Create a simple sample scene."""
    return SceneData(
        scene_number=1,
        title="シンプルなシーン",
        description="説明文",
        characters=["主人公"],
        events=["イベント"],
    )


@pytest.fixture
def sample_scene_data_detailed() -> SceneData:
    """Create a detailed sample scene with all fields."""
    return SceneData(
        scene_number=1,
        title="詳細なシーン",
        description="詳しい説明文です。主人公がヒロインと出会うシーンです。",
        characters=["主人公（25歳）", "ヒロイン（23歳）"],
        events=["初対面", "言葉の交換", "共通点発見"],
        location="図書館の古書コーナー",
        time_setting="秋の午後3時",
    )


@pytest.fixture
def sample_multiple_scenes() -> list[SceneData]:
    """Create multiple sample scenes for sequence testing."""
    return [
        SceneData(
            scene_number=1,
            title="出会い",
            description="主人公がヒロインと出会う",
            characters=["主人公", "ヒロイン"],
            events=["初対面"],
            location="図書館",
            time_setting="午後",
        ),
        SceneData(
            scene_number=2,
            title="発展",
            description="関係が深まる",
            characters=["主人公", "ヒロイン"],
            events=["会話"],
            location="カフェ",
            time_setting="夕方",
        ),
        SceneData(
            scene_number=3,
            title="転機",
            description="重大な出来事",
            characters=["主人公", "ヒロイン"],
            events=["告白"],
            location="公園",
            time_setting="夜",
        ),
    ]


@pytest.fixture
def sample_episode_context() -> dict:
    """Create sample episode context."""
    return {
        "title": "第001話",
        "theme": "新しい始まり",
        "tone": "ロマンティック",
        "total_scenes": 3,
    }


@pytest.fixture
def temp_project_root(tmp_path) -> Path:
    """Create a temporary project root directory."""
    project_root = tmp_path / "project"
    project_root.mkdir(exist_ok=True)
    return project_root


@pytest.fixture(autouse=True)
def reset_test_state():
    """Reset any test state before each test."""
    return
    # Cleanup after test


# Markers for test categorization
def pytest_configure(config):
    """Register custom markers."""
    config.addinivalue_line(
        "markers", "happy_path: tests for normal successful scenarios"
    )
    config.addinivalue_line(
        "markers", "error_case: tests for error handling and exceptions"
    )
    config.addinivalue_line(
        "markers", "boundary: tests for boundary values and edge cases"
    )
    config.addinivalue_line(
        "markers", "whitebox: tests for internal code path coverage"
    )
    config.addinivalue_line(
        "markers", "integration: integration tests across components"
    )
