#!/usr/bin/env python3
# File: src/noveler/infrastructure/repositories/yaml_chapter_plot_repository.py
# Purpose: Persist chapter plot data using YAML files with legacy naming compatibility.
# Context: Infrastructure repository consumed by application services and domain workflows.

"""YAML-backed chapter plot repository with graceful fallbacks."""

from __future__ import annotations

from collections.abc import Iterable
from pathlib import Path
from typing import Any

import yaml

from noveler.domain.entities.chapter_plot import ChapterPlot
from noveler.domain.repositories.chapter_plot_repository import (
    ChapterPlotNotFoundError,
    ChapterPlotRepository,
)
from noveler.domain.value_objects.chapter_number import ChapterNumber
from noveler.presentation.shared.shared_utilities import get_common_path_service

CHAPTER_SUBDIR_NAME = "章別プロット"
MISSING_FILE_MESSAGE = "章別プロットファイルが見つかりません"
READ_ERROR_MESSAGE = "章別プロットファイルの読み込みに失敗しました"
PROCESSING_ERROR_MESSAGE = "章別プロットファイルの処理に失敗しました"
MISSING_FIELDS_MESSAGE = "必須フィールドが不足しています"
EPISODE_NOT_FOUND_MESSAGE = "エピソード{episode}を含む章別プロットが見つかりません"


class YamlChapterPlotRepository(ChapterPlotRepository):
    """Repository that loads chapter plots from YAML files."""

    def __init__(self, project_root: Path) -> None:
        super().__init__(project_root)
        self._path_service = get_common_path_service(project_root)

    # ---------------------------------------------------------------- lookups --
    def find_by_chapter_number(self, chapter_number: ChapterNumber) -> ChapterPlot:
        file_path = self.get_chapter_plot_file_path(chapter_number)
        if not file_path.exists():
            raise ChapterPlotNotFoundError(f"{MISSING_FILE_MESSAGE}: {file_path}")

        try:
            yaml_text = file_path.read_text(encoding="utf-8")
            data: dict[str, Any] = yaml.safe_load(yaml_text) or {}
        except yaml.YAMLError as exc:
            raise ChapterPlotNotFoundError(f"{READ_ERROR_MESSAGE}: {file_path}\n原因: {exc}") from exc
        except Exception as exc:
            raise ChapterPlotNotFoundError(f"{PROCESSING_ERROR_MESSAGE}: {file_path}\n原因: {exc}") from exc

        return self._parse_yaml_to_chapter_plot(data)

    def find_by_episode_number(self, episode_number: int) -> ChapterPlot:
        chapter_number = ChapterNumber.from_episode_number(episode_number)
        try:
            chapter_plot = self.find_by_chapter_number(chapter_number)
            if chapter_plot.contains_episode(episode_number):
                return chapter_plot
        except ChapterPlotNotFoundError:
            pass
        return self._search_episode_in_all_chapters(episode_number)

    def exists(self, chapter_number: ChapterNumber) -> bool:
        return self.get_chapter_plot_file_path(chapter_number).exists()

    def list_all(self) -> list[ChapterPlot]:
        directory = self._get_chapter_plot_directory()
        patterns = ("chapter*.yaml", "ch*.yaml", "章*話*.yaml")
        files: list[Path] = []
        for pattern in patterns:
            files = list(directory.glob(pattern))
            if files:
                break

        plots: list[ChapterPlot] = []
        for file_path in sorted(files):
            try:
                yaml_text = file_path.read_text(encoding="utf-8")
                data: dict[str, Any] = yaml.safe_load(yaml_text) or {}
                plots.append(self._parse_yaml_to_chapter_plot(data))
            except Exception:
                continue
        return sorted(plots, key=lambda plot: plot.chapter_number.value)

    # ----------------------------------------------------------- path helpers --
    def get_chapter_plot_file_path(self, chapter_number: ChapterNumber) -> Path:
        directory = self._get_chapter_plot_directory()
        canonical = directory / f"chapter{chapter_number.value:02d}.yaml"
        candidates = [
            canonical,
            directory / f"ch{chapter_number.value:02d}.yaml",
            directory / f"ch{chapter_number.value:03d}.yaml",
            directory / f"章{chapter_number.value:02d}話.yaml",
            directory / f"章{chapter_number.value}話.yaml",
        ]
        for candidate in candidates:
            if candidate.exists():
                return candidate
        return canonical

    def _get_chapter_plot_directory(self) -> Path:
        plots_dir = self._path_service.get_plots_dir()
        if not plots_dir.is_absolute():
            plots_dir = self.project_root / plots_dir
        return plots_dir / CHAPTER_SUBDIR_NAME

    # --------------------------------------------------------- episode search --
    def _search_episode_in_all_chapters(self, episode_number: int) -> ChapterPlot:
        for chapter_plot in self.list_all():
            if chapter_plot.contains_episode(episode_number):
                return chapter_plot
        raise ChapterPlotNotFoundError(
            EPISODE_NOT_FOUND_MESSAGE.format(episode=episode_number),
        )

    # ----------------------------------------------------------- YAML parsing --
    def _parse_yaml_to_chapter_plot(self, data: dict[str, Any]) -> ChapterPlot:
        required = ("chapter_number", "title", "summary")
        missing = [field for field in required if field not in data]
        if missing:
            raise ChapterPlotNotFoundError(f"{MISSING_FIELDS_MESSAGE}: {', '.join(missing)}")

        try:
            chapter_number = ChapterNumber(data["chapter_number"])
        except Exception as exc:
            raise ChapterPlotNotFoundError(f"章番号の変換に失敗しました: {exc}") from exc

        key_events = self._ensure_string_list(data.get("key_events", []))
        episodes = self._normalise_episode_entries(data.get("episodes", []))

        chapter_info = data.get("chapter_info", {})
        if isinstance(chapter_info, dict):
            central_theme = str(chapter_info.get("central_theme") or data.get("central_theme") or "")
            viewpoint_management = chapter_info.get("viewpoint_management") or data.get("viewpoint_management") or {}
        else:
            central_theme = str(data.get("central_theme", ""))
            viewpoint_management = data.get("viewpoint_management", {})

        return ChapterPlot(
            chapter_number=chapter_number,
            title=str(data["title"]),
            summary=str(data["summary"]),
            key_events=key_events,
            episodes=episodes,
            central_theme=central_theme,
            viewpoint_management=viewpoint_management if isinstance(viewpoint_management, dict) else {},
        )

    def _ensure_string_list(self, values: Iterable[Any]) -> list[str]:
        result: list[str] = []
        for value in values:
            if isinstance(value, str):
                result.append(value)
            else:
                result.append(str(value))
        return result

    def _normalise_episode_entries(self, episodes: Any) -> list[dict[str, Any]]:
        if not isinstance(episodes, Iterable):
            return []
        normalised: list[dict[str, Any]] = []
        for entry in episodes:
            if isinstance(entry, dict):
                normalised.append(entry)
            else:
                normalised.append({"episode_number": entry})
        return normalised


__all__ = ["YamlChapterPlotRepository"]
