#!/usr/bin/env python3
"""PlotDisplayService ユニットテスト

執筆前プロット表示機能のテスト
SPEC-PLOT-ADHERENCE-001 Section 2.1 準拠
"""

from unittest.mock import MagicMock

import pytest

from noveler.application.services.plot_display_service import PlotDisplayService
from noveler.application.validators.plot_adherence_validator import AdherenceElementType


@pytest.mark.spec("SPEC-PLOT-ADHERENCE-001")
class TestPlotDisplayService:
    """PlotDisplayService のテストクラス"""

    # ========================================
    # 正常系テスト
    # ========================================

    def test_extract_plot_elements_valid_data(self):
        """正常系: 有効なプロットデータから要素抽出"""
        service = PlotDisplayService()

        plot_data = {
            "key_events": ["ギルド到着", "初依頼受注"],
            "character_development": ["主人公の緊張"],
            "world_building": ["受付嬢の描写"],
            "foreshadowing": ["謎の指輪"],
        }

        elements = service._extract_plot_elements(plot_data)

        # 要素数確認（2+1+1+1=5個）
        assert len(elements) == 5

        # 要素タイプ別のカウント
        key_events = [e for e in elements if e.element_type == AdherenceElementType.KEY_EVENT]
        char_dev = [e for e in elements if e.element_type == AdherenceElementType.CHARACTER_DEVELOPMENT]
        world = [e for e in elements if e.element_type == AdherenceElementType.WORLD_BUILDING]
        foreshadow = [e for e in elements if e.element_type == AdherenceElementType.FORESHADOWING]

        assert len(key_events) == 2
        assert len(char_dev) == 1
        assert len(world) == 1
        assert len(foreshadow) == 1

    def test_extract_plot_elements_with_empty_lists(self):
        """正常系: 空のリストを含むデータ"""
        service = PlotDisplayService()

        plot_data = {
            "key_events": ["イベント"],
            "character_development": [],
            "world_building": [],
            "foreshadowing": [],
        }

        elements = service._extract_plot_elements(plot_data)

        # 1個のみ抽出
        assert len(elements) == 1
        assert elements[0].element_type == AdherenceElementType.KEY_EVENT
        assert elements[0].description == "イベント"

    def test_display_plot_elements_normal_flow(self):
        """正常系: プロット要素の表示フロー"""
        mock_console = MagicMock()
        service = PlotDisplayService(console_service=mock_console)

        plot_data = {
            "key_events": ["イベント1", "イベント2"],
            "character_development": ["キャラ"],
            "world_building": ["世界"],
            "foreshadowing": ["伏線"],
        }

        # Act
        service.display_plot_elements(episode_number=1, plot_data=plot_data)

        # Assert
        assert mock_console.print.called

    # ========================================
    # 異常系テスト
    # ========================================

    def test_extract_plot_elements_missing_key_events(self):
        """異常系: key_events キーが欠けている"""
        service = PlotDisplayService()

        plot_data = {
            "character_development": ["キャラ"],
            "world_building": ["世界"],
            "foreshadowing": ["伏線"],
            # key_events なし
        }

        with pytest.raises(ValueError, match="必須キーが欠けています"):
            service._extract_plot_elements(plot_data)

    def test_extract_plot_elements_missing_character_development(self):
        """異常系: character_development キーが欠けている"""
        service = PlotDisplayService()

        plot_data = {
            "key_events": ["イベント"],
            "world_building": ["世界"],
            "foreshadowing": ["伏線"],
            # character_development なし
        }

        with pytest.raises(ValueError, match="必須キーが欠けています"):
            service._extract_plot_elements(plot_data)

    def test_extract_plot_elements_key_events_none(self):
        """異常系: key_events が None"""
        service = PlotDisplayService()

        plot_data = {
            "key_events": None,
            "character_development": ["キャラ"],
            "world_building": ["世界"],
            "foreshadowing": ["伏線"],
        }

        with pytest.raises(ValueError, match="key_eventsがNullです"):
            service._extract_plot_elements(plot_data)

    def test_extract_plot_elements_key_events_not_list(self):
        """異常系: key_events がリスト型ではない"""
        service = PlotDisplayService()

        plot_data = {
            "key_events": "not_a_list",  # type: ignore
            "character_development": ["キャラ"],
            "world_building": ["世界"],
            "foreshadowing": ["伏線"],
        }

        with pytest.raises(ValueError, match="リスト型である必要があります"):
            service._extract_plot_elements(plot_data)

    def test_extract_plot_elements_character_development_none(self):
        """異常系: character_development が None"""
        service = PlotDisplayService()

        plot_data = {
            "key_events": ["イベント"],
            "character_development": None,
            "world_building": ["世界"],
            "foreshadowing": ["伏線"],
        }

        with pytest.raises(ValueError, match="character_developmentがNullです"):
            service._extract_plot_elements(plot_data)

    def test_extract_plot_elements_world_building_none(self):
        """異常系: world_building が None"""
        service = PlotDisplayService()

        plot_data = {
            "key_events": ["イベント"],
            "character_development": ["キャラ"],
            "world_building": None,
            "foreshadowing": ["伏線"],
        }

        with pytest.raises(ValueError, match="world_buildingがNullです"):
            service._extract_plot_elements(plot_data)

    def test_extract_plot_elements_foreshadowing_none(self):
        """異常系: foreshadowing が None"""
        service = PlotDisplayService()

        plot_data = {
            "key_events": ["イベント"],
            "character_development": ["キャラ"],
            "world_building": ["世界"],
            "foreshadowing": None,
        }

        with pytest.raises(ValueError, match="foreshadowingがNullです"):
            service._extract_plot_elements(plot_data)

    def test_extract_plot_elements_world_building_not_list(self):
        """異常系: world_building がリスト型ではない"""
        service = PlotDisplayService()

        plot_data = {
            "key_events": ["イベント"],
            "character_development": ["キャラ"],
            "world_building": {"nested": "dict"},  # type: ignore
            "foreshadowing": ["伏線"],
        }

        with pytest.raises(ValueError, match="リスト型である必要があります"):
            service._extract_plot_elements(plot_data)

    def test_display_plot_elements_invalid_format(self):
        """異常系: 無効なデータ形式"""
        mock_console = MagicMock()
        service = PlotDisplayService(console_service=mock_console)

        plot_data = "invalid"  # type: ignore

        service.display_plot_elements(episode_number=1, plot_data=plot_data)

        # エラーメッセージが表示される
        mock_console.print_error.assert_called_once()

    # ========================================
    # 境界値テスト
    # ========================================

    def test_extract_plot_elements_many_elements(self):
        """境界値: 大量の要素（各タイプ20個）"""
        service = PlotDisplayService()

        plot_data = {
            "key_events": [f"イベント{i}" for i in range(20)],
            "character_development": [f"キャラ{i}" for i in range(20)],
            "world_building": [f"世界{i}" for i in range(20)],
            "foreshadowing": [f"伏線{i}" for i in range(20)],
        }

        elements = service._extract_plot_elements(plot_data)

        # 80個の要素が抽出される
        assert len(elements) == 80

    def test_extract_plot_elements_long_description(self):
        """境界値: 非常に長い説明文"""
        service = PlotDisplayService()

        long_desc = "あ" * 500

        plot_data = {
            "key_events": [long_desc],
            "character_development": [],
            "world_building": [],
            "foreshadowing": [],
        }

        elements = service._extract_plot_elements(plot_data)

        assert len(elements) == 1
        assert elements[0].description == long_desc

    def test_display_plot_elements_episode_number_variants(self):
        """境界値: 様々なエピソード番号"""
        mock_console = MagicMock()
        service = PlotDisplayService(console_service=mock_console)

        plot_data = {
            "key_events": ["イベント"],
            "character_development": [],
            "world_building": [],
            "foreshadowing": [],
        }

        # エピソード1
        service.display_plot_elements(episode_number=1, plot_data=plot_data)
        assert mock_console.print.called

        # エピソード999
        service.display_plot_elements(episode_number=999, plot_data=plot_data)
        assert mock_console.print.called

        # エピソード0
        service.display_plot_elements(episode_number=0, plot_data=plot_data)
        assert mock_console.print.called
