#!/usr/bin/env python3

"""Integration tests for ManuscriptGeneratorService scene-based LLM generation.

Tests the full workflow of scene-based manuscript generation with LLM integration,
including multi-scene generation, context propagation across scenes, and
end-to-end flow validation.
"""

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

import pytest

from noveler.domain.interfaces.progressive_write_llm_executor import (
    LLMExecutionRequest,
    LLMExecutionResult,
)
from noveler.domain.services.writing_steps.manuscript_generator_service import (
    ManuscriptGeneratorService,
    ManuscriptGenerationWorkspace,
)
from noveler.domain.services.writing_steps.plot_analyzer_service import (
    PlotAnalysisResult,
)
from noveler.domain.value_objects.scene_data import SceneData
from noveler.domain.value_objects.project_time import project_now


class MultiSceneRealismMockExecutor:
    """Mock executor that simulates realistic multi-scene generation."""

    def __init__(self, total_scenes: int = 3, fail_on_scene: int | None = None):
        """Initialize realistic mock.

        Args:
            total_scenes: Total number of scenes to generate
            fail_on_scene: Optional scene number to simulate failure on
        """
        self.total_scenes = total_scenes
        self.fail_on_scene = fail_on_scene
        self.call_count = 0
        self.calls = []

    async def run(self, request: LLMExecutionRequest) -> LLMExecutionResult:
        """Execute realistic mock response."""
        self.call_count += 1
        scene_number = request.input_data.get("scene_number", 0)

        self.calls.append({
            "call_number": self.call_count,
            "scene_number": scene_number,
            "step_name": request.step_name,
        })

        # Simulate failure on specific scene
        if self.fail_on_scene and scene_number == self.fail_on_scene:
            return LLMExecutionResult(
                success=False,
                response_content="",
                error_message=f"Simulated failure on scene {scene_number}",
                extracted_data={},
                metadata={},
            )

        # Return realistic content based on scene number
        variations = [
            "主人公が目覚めた。周囲を見回すと、見知らぬ場所だった。心当たりがない。不安がこみ上げる。昨日は何があったのだろう。記憶が曖昧だ。",
            "ドアがゆっくり開く。現れたのは予想外の人物だった。思わず身構える。相手も同じように警戒している。一瞬の沈黙。緊張が走る。",
            "二人の間に何かが起こった。微妙な変化。視線が交差する。心臓の鼓動が高まる。言葉が出ない。ただ相手を見つめるだけだ。",
            "事態は予想以上に複雑だった。様々な要素が絡み合っている。解きほぐす糸口が見つからない。前に進むしかない。覚悟を決める。",
            "突然、光が差し込んだ。全てが明らかになる瞬間。驚愕と同時に納得する。これまでの疑問が解ける。新たな視点が開ける。",
        ]

        # Select variation based on scene (cycling through available)
        variation_idx = (scene_number - 1) % len(variations)
        base_content = variations[variation_idx]

        # Repeat to reach target word count (~2000 chars)
        full_content = (base_content + "\n\n") * 8

        return LLMExecutionResult(
            success=True,
            response_content=full_content,
            extracted_data={},
            metadata={"tokens_used": 1500, "scene": scene_number},
        )

    def run_sync(self, request: LLMExecutionRequest) -> LLMExecutionResult:
        """Synchronous variant."""
        raise NotImplementedError("Use async variant")


class TestMultiSceneGenerationFlow:
    """Test: Multi-scene generation end-to-end flow."""

    @pytest.fixture
    def service(self):
        """Create service with mocked dependencies."""
        mock_logger = Mock()
        mock_logger.info = Mock()
        return ManuscriptGeneratorService(logger_service=mock_logger)

    @pytest.fixture
    def sample_scenes(self) -> list[SceneData]:
        """Create sample scenes for multi-scene generation."""
        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.mark.asyncio
    async def test_three_scene_generation_sequential(
        self, service, sample_scenes
    ):
        """Test: Three scenes are generated sequentially with context."""
        # Arrange
        executor = MultiSceneRealismMockExecutor(total_scenes=3)
        episode_context = {
            "title": "第001話",
            "theme": "新しい始まり",
            "total_scenes": 3,
        }

        # Act: Generate each scene
        generated_contents = []
        context_summary = ""

        for scene in sample_scenes:
            content = await service.convert_scene_to_content_with_llm(
                scene=scene,
                previous_scenes_summary=context_summary,
                episode_context=episode_context,
                llm_executor=executor,
                project_root=Path("/tmp"),
                episode_number=1,
                target_word_count=2000,
            )
            generated_contents.append(content)
            context_summary = service._build_context_summary(generated_contents)

        # Assert
        assert len(generated_contents) == 3, "All 3 scenes should be generated"
        assert all(len(c) > 300 for c in generated_contents), "All content > 300 chars"
        assert executor.call_count == 3, "LLM should be called 3 times"

        # Verify context was passed to each subsequent scene
        assert executor.calls[0]["scene_number"] == 1
        assert executor.calls[1]["scene_number"] == 2
        assert executor.calls[2]["scene_number"] == 3

    @pytest.mark.asyncio
    async def test_twelve_scene_generation_realistic(self, service):
        """Test: Maximum 12 scenes can be generated sequentially."""
        # Arrange: Create 12 sample scenes
        scenes = [
            SceneData(
                scene_number=i,
                title=f"シーン{i}",
                description=f"シーン{i}の説明",
                characters=["A", "B"],
                events=[f"イベント{i}"],
            )
            for i in range(1, 13)
        ]
        executor = MultiSceneRealismMockExecutor(total_scenes=12)
        episode_context = {"title": "第001話", "total_scenes": 12}

        # Act
        generated_count = 0
        context = ""
        for scene in scenes:
            try:
                content = await service.convert_scene_to_content_with_llm(
                    scene=scene,
                    previous_scenes_summary=context,
                    episode_context=episode_context,
                    llm_executor=executor,
                    project_root=Path("/tmp"),
                    episode_number=1,
                )
                generated_count += 1
                context = service._build_context_summary([content])
            except Exception:
                break

        # Assert
        assert generated_count == 12, "All 12 scenes should be generated"
        assert executor.call_count == 12

    @pytest.mark.asyncio
    async def test_context_propagation_chain(self, service, sample_scenes):
        """Test: Context is properly propagated through scene chain."""
        # Arrange
        executor = MultiSceneRealismMockExecutor(total_scenes=3)
        episode_context = {"title": "第001話", "total_scenes": 3}

        # Act: Generate with explicit context tracking
        contents = []
        contexts = [""]  # Start with empty context

        for i, scene in enumerate(sample_scenes):
            content = await service.convert_scene_to_content_with_llm(
                scene=scene,
                previous_scenes_summary=contexts[-1],
                episode_context=episode_context,
                llm_executor=executor,
                project_root=Path("/tmp"),
                episode_number=1,
            )
            contents.append(content)
            new_context = service._build_context_summary(contents)
            contexts.append(new_context)

        # Assert
        assert len(contents) == 3
        # Each context should be different (built from cumulative content)
        assert contexts[0] == ""
        assert len(contexts[1]) > 0, "Context for scene 2 should be non-empty"
        assert len(contexts[2]) > 0, "Context for scene 3 should be non-empty"

    @pytest.mark.asyncio
    async def test_generation_resilience_to_failure(self, service, sample_scenes):
        """Test: Generation can continue even if one scene fails initially."""
        # Arrange
        executor = MultiSceneRealismMockExecutor(
            total_scenes=3, fail_on_scene=2  # Fail on scene 2
        )
        episode_context = {"title": "第001話", "total_scenes": 3}

        # Act
        results = {
            "success": 0,
            "failed": 0,
            "fallback": 0,
        }
        context = ""

        for scene in sample_scenes:
            try:
                content = await service.convert_scene_to_content_with_llm(
                    scene=scene,
                    previous_scenes_summary=context,
                    episode_context=episode_context,
                    llm_executor=executor,
                    project_root=Path("/tmp"),
                    episode_number=1,
                    max_retries=1,
                )
                results["success"] += 1
                context = service._build_context_summary([content])
            except RuntimeError:
                # Fallback to template
                results["failed"] += 1
                template = service.convert_scene_to_content(scene)
                results["fallback"] += 1
                context = service._build_context_summary([template])

        # Assert
        assert results["success"] >= 2, "At least 2 scenes should succeed"
        assert results["fallback"] >= 1, "Fallback should be used for failed scene"


class TestSceneBasedManuscriptGeneration:
    """Test: Full _generate_scene_based_manuscript() workflow."""

    @pytest.fixture
    def service(self):
        """Create service."""
        return ManuscriptGeneratorService(logger_service=Mock())

    @pytest.fixture
    def workspace(self) -> ManuscriptGenerationWorkspace:
        """Create sample workspace."""
        scenes = [
            SceneData(
                scene_number=i,
                title=f"シーン{i}",
                description=f"説明{i}",
                characters=["A", "B"],
                events=[f"イベント{i}"],
            )
            for i in range(1, 4)
        ]

        workspace = ManuscriptGenerationWorkspace(
            episode_number=1,
            project_root=Path("/tmp"),
            target_word_count=6000,
            episode_title="第001話",
            scenes=scenes,
        )
        workspace.plot_analysis_result = PlotAnalysisResult(
            episode_number=1,
            plot_exists=True,
            analysis_confidence=0.85,
            main_conflicts=["対立1"],
            key_events=["イベントA", "イベントB"],
            genre_elements=["ロマンティック"],
        )
        return workspace

    @pytest.mark.asyncio
    async def test_generate_scene_based_manuscript_llm_mode(
        self, service, workspace
    ):
        """Test: Generate manuscript in LLM mode."""
        # Arrange
        executor = MultiSceneRealismMockExecutor(total_scenes=3)

        # Act
        result = await service._generate_scene_based_manuscript(
            workspace,
            llm_executor=executor,
            use_llm=True,
        )

        # Assert
        assert len(result) > 1000, "Result should be substantial"
        assert "第001話" in result, "Should include episode title"
        assert "主人公" in result or "対立1" in result, "Should include scene content or metadata"
        assert "LLM統合" in result, "Should indicate LLM mode"

    @pytest.mark.asyncio
    async def test_generate_scene_based_manuscript_template_mode(
        self, service, workspace
    ):
        """Test: Generate manuscript in template mode (fallback)."""
        # Arrange
        executor = None

        # Act
        result = await service._generate_scene_based_manuscript(
            workspace,
            llm_executor=executor,
            use_llm=False,
        )

        # Assert
        assert len(result) > 500, "Template result should be present"
        assert "第001話" in result
        assert "テンプレート" in result, "Should indicate template mode"

    @pytest.mark.asyncio
    async def test_manuscript_generation_respects_word_count(
        self, service, workspace
    ):
        """Test: Target word count is respected."""
        # Arrange
        workspace.target_word_count = 6000
        num_scenes = len(workspace.scenes)
        expected_per_scene = 6000 // num_scenes  # ~2000 per scene

        executor = MultiSceneRealismMockExecutor(total_scenes=num_scenes)

        # Act
        result = await service._generate_scene_based_manuscript(
            workspace,
            llm_executor=executor,
            use_llm=True,
        )

        # Assert
        # Result should contain generated content and metadata
        assert len(result) > 1000, "Should generate substantial content"
        assert f"{expected_per_scene}" in result, "Word count info present"


class TestErrorRecoveryIntegration:
    """Test: Error recovery across multi-scene generation."""

    @pytest.fixture
    def service(self):
        """Create service."""
        return ManuscriptGeneratorService(logger_service=Mock())

    @pytest.mark.asyncio
    async def test_partial_failure_recovery(self, service):
        """Test: Generation continues after partial failures."""
        # Arrange
        scenes = [
            SceneData(
                scene_number=i,
                title=f"シーン{i}",
                description=f"説明{i}",
                characters=["A"],
                events=["event"],
            )
            for i in range(1, 4)
        ]

        executor = MultiSceneRealismMockExecutor(total_scenes=3)

        # Act: Generate with intentional failure handling
        successful = []
        for scene in scenes:
            try:
                content = await service.convert_scene_to_content_with_llm(
                    scene=scene,
                    llm_executor=executor,
                    project_root=Path("/tmp"),
                    episode_number=1,
                    max_retries=2,
                )
                successful.append(content)
            except RuntimeError:
                # Record failure but continue
                pass

        # Assert
        assert len(successful) >= 2, "Should recover from failures"

    @pytest.mark.asyncio
    async def test_graceful_degradation_to_template(self, service):
        """Test: Gracefully falls back to template on LLM failure."""
        # Arrange
        scene = SceneData(
            scene_number=1,
            title="Test",
            description="Test",
            characters=["A"],
            events=["event"],
        )
        executor = Mock()
        executor.run = AsyncMock(side_effect=Exception("API Error"))

        # Act: LLM fails
        with pytest.raises(RuntimeError):
            await service.convert_scene_to_content_with_llm(
                scene=scene,
                llm_executor=executor,
                project_root=Path("/tmp"),
                episode_number=1,
                max_retries=1,
            )

        # Act: Fallback to template
        template = service.convert_scene_to_content(scene)

        # Assert
        assert len(template) > 100, "Template should provide structure"
        assert "Test" in template


class TestPromptQualityIntegration:
    """Test: Prompt quality across integration flow."""

    @pytest.fixture
    def service(self):
        """Create service."""
        return ManuscriptGeneratorService(logger_service=Mock())

    def test_full_prompt_generation_with_all_context(self, service):
        """Test: Prompt includes all context information."""
        # Arrange
        scene = SceneData(
            scene_number=2,
            title="重大な転機",
            description="人生を変える出来事",
            characters=["主人公", "ヒロイン"],
            events=["告白", "別れ"],
            location="駅前のベンチ",
            time_setting="夜中の2時",
        )
        previous_context = "前のシーンで主人公はヒロインへの気持ちに気づいた。"
        episode_context = {
            "title": "第005話 別れのシーン",
            "theme": "切ない決別",
            "tone": "メランコリック",
            "total_scenes": 8,
        }

        # Act
        prompt = service._build_scene_prompt(
            scene=scene,
            previous_context=previous_context,
            episode_context=episode_context,
            target_word_count=2500,
        )

        # Assert: All information is present
        assert "重大な転機" in prompt
        assert "主人公" in prompt
        assert "告白" in prompt
        assert "駅前のベンチ" in prompt
        assert "夜中の2時" in prompt
        assert previous_context in prompt or "前のシーン" in prompt
        assert "第005話" in prompt or "別れのシーン" in prompt
        assert "メランコリック" in prompt
        assert "2500" in prompt

        # Verify prompt is well-structured
        assert len(prompt) > 300, "Prompt should be comprehensive"
        assert "【" in prompt or "シーン" in prompt, "Should have structure markers"
