#!/usr/bin/env python3

"""Application.services.llm_improvement_suggestion_service
Where: LLM-based improvement suggestion service for plot adherence.
What: Generates detailed, context-aware improvement suggestions using Claude LLM.
Why: Provides specific, actionable feedback beyond generic templates.
"""

from __future__ import annotations

"""LLMベース改善提案生成サービス

プロット準拠検証の結果に基づいて、LLMを使用して具体的な改善提案を生成する
SPEC-PLOT-ADHERENCE-001 Section 2.2.4 高度版実装
"""

import json
from typing import TYPE_CHECKING, Any

from noveler.application.adapters.cli_adapter_factory import CliAdapterFactory
from noveler.application.validators.plot_adherence_validator import AdherenceElementType, PlotElement

if TYPE_CHECKING:
    from noveler.domain.interfaces.i_logger_service import ILoggerService
    from noveler.infrastructure.integrations.session_executor import ISessionExecutor

logger = CliAdapterFactory.create_logger(__name__)


class LLMImprovementSuggestionService:
    """LLMベース改善提案サービス

    プロット準拠検証の検証結果からLLMを使用して、
    具体的で実行可能な改善提案を生成する。
    """

    # LLM設定
    DEFAULT_TEMPERATURE = 0.3  # 低めの温度で安定した提案
    DEFAULT_TIMEOUT = 30  # 秒

    def __init__(
        self,
        session_executor: ISessionExecutor | None = None,
        logger_service: ILoggerService | None = None,
    ) -> None:
        """初期化

        Args:
            session_executor: LLMセッション実行者（任意）
            logger_service: ロガーサービス（任意）
        """
        if session_executor is None:
            from noveler.infrastructure.services.unified_session_executor import UnifiedSessionExecutor

            session_executor = UnifiedSessionExecutor()
        if logger_service is None:
            from noveler.domain.interfaces.logger_service import NullLoggerService

            logger_service = NullLoggerService()

        self._session_executor = session_executor
        self._logger_service = logger_service
        self._module_logger = logger
        self._temperature = self.DEFAULT_TEMPERATURE
        self._timeout = self.DEFAULT_TIMEOUT

    async def generate_suggestions(
        self,
        manuscript: str,
        missing_elements: list[PlotElement],
        extra_elements: list[PlotElement],
        adherence_score: float,
    ) -> list[str]:
        """改善提案を生成

        Args:
            manuscript: 執筆済み原稿
            missing_elements: 不足要素のリスト
            extra_elements: 過剰要素のリスト（参考用）
            adherence_score: 準拠率スコア

        Returns:
            list[str]: 改善提案リスト

        Raises:
            ValueError: LLMセッションが利用できない場合
        """
        if not missing_elements:
            # 不足要素がない場合は、準拠率に基づく一般的な提案のみ
            return self._get_generic_suggestions(adherence_score)

        try:
            self._module_logger.info("LLMベース改善提案生成開始: %d個の不足要素", len(missing_elements))

            # プロンプト構築
            prompt = self._build_improvement_prompt(
                manuscript=manuscript,
                missing_elements=missing_elements,
                adherence_score=adherence_score,
            )

            # LLM実行
            response = self._session_executor.execute_prompt(
                prompt=prompt,
                response_format="json",
                timeout=self._timeout,
            )

            # 応答パース
            suggestions = self._parse_llm_response(response)

            # 一般提案の追加
            generic = self._get_generic_suggestions(adherence_score)
            suggestions.extend(generic)

            self._module_logger.info("改善提案生成完了: %d個の提案", len(suggestions))
            return suggestions

        except Exception as e:
            self._module_logger.warning("LLM改善提案生成失敗、テンプレート提案を使用: %s", e)
            # フォールバック: テンプレートベース提案
            return self._get_template_suggestions(missing_elements, adherence_score)

    def _build_improvement_prompt(
        self,
        manuscript: str,
        missing_elements: list[PlotElement],
        adherence_score: float,
    ) -> str:
        """改善提案生成用プロンプト構築

        Args:
            manuscript: 執筆済み原稿
            missing_elements: 不足要素
            adherence_score: 準拠率

        Returns:
            str: LLMプロンプト
        """
        # 不足要素の説明文を抽出
        missing_descriptions = "\n".join(
            f"- [{e.element_type.value}] {e.description}" for e in missing_elements
        )

        # 原稿の先頭部分をコンテキストとして使用（トークン節約）
        manuscript_excerpt = manuscript[:2000]  # 最初の2000文字

        prompt = f"""あなたはプロット構成と小説執筆の専門家です。
以下の原稿と不足しているプロット要素に基づいて、
具体的で実行可能な改善提案を日本語で生成してください。

【原稿の一部】
{manuscript_excerpt}
...

【現在の準拠率】
{adherence_score:.1f}%

【不足しているプロット要素】
{missing_descriptions}

【提案の要件】
1. 各不足要素について、原稿のどの部分を修正すべきかを具体的に指摘
2. 修正方法を3-5文の具体的な説明として提供
3. 修正による効果を簡潔に説明
4. JSON形式で以下の構造で返す:

{{
  "suggestions": [
    {{
      "element_type": "要素タイプ",
      "description": "不足している要素",
      "improvement_text": "具体的な改善提案",
      "impact": "修正による効果",
      "priority": "高/中/低"
    }}
  ]
}}

実際の小説執筆のプロに合わせた、実現可能で価値のある提案を生成してください。"""

        return prompt

    def _parse_llm_response(self, response: str) -> list[str]:
        """LLM応答をパース

        Args:
            response: LLM応答テキスト

        Returns:
            list[str]: 改善提案リスト

        Raises:
            ValueError: 応答形式が不正な場合
        """
        try:
            # JSON抽出
            data = json.loads(response)

            if not isinstance(data, dict) or "suggestions" not in data:
                msg = "LLM応答形式が不正です: suggestionsキーがありません"
                raise ValueError(msg)

            suggestions_data = data["suggestions"]
            if not isinstance(suggestions_data, list):
                msg = "LLM応答形式が不正です: suggestionsはリストである必要があります"
                raise ValueError(msg)

            # 提案文を整形
            suggestions: list[str] = []
            for item in suggestions_data:
                if not isinstance(item, dict):
                    continue

                element_type = item.get("element_type", "不明")
                improvement = item.get("improvement_text", "")
                impact = item.get("impact", "")
                priority = item.get("priority", "中")

                if improvement:
                    # 優先度アイコン
                    icon = "🔴" if priority == "高" else "🟡" if priority == "中" else "🟢"

                    suggestion = f"{icon} [{element_type}] {improvement}"
                    if impact:
                        suggestion += f"\n   効果: {impact}"

                    suggestions.append(suggestion)

            return suggestions

        except (json.JSONDecodeError, ValueError) as e:
            self._module_logger.debug("LLM応答パースエラー: %s", e)
            msg = f"LLM応答のパースに失敗しました: {e}"
            raise ValueError(msg) from e

    def _get_template_suggestions(
        self,
        missing_elements: list[PlotElement],
        adherence_score: float,
    ) -> list[str]:
        """テンプレートベースの改善提案（フォールバック）

        Args:
            missing_elements: 不足要素
            adherence_score: 準拠率

        Returns:
            list[str]: 改善提案リスト
        """
        suggestions: list[str] = []

        # 要素タイプ別テンプレート
        template_map = {
            AdherenceElementType.KEY_EVENT: "重要イベントの描写を追加: {}",
            AdherenceElementType.CHARACTER_DEVELOPMENT: "キャラクター描写を強化: {}",
            AdherenceElementType.WORLD_BUILDING: "世界観描写を追加: {}",
            AdherenceElementType.FORESHADOWING: "伏線の設置を検討: {}",
        }

        for missing in missing_elements:
            template = template_map.get(missing.element_type, "要素を追加: {}")
            suggestions.append(template.format(missing.description))

        # 準拠率に基づく全体提案
        if adherence_score < 80.0:
            suggestions.append("プロットの見直しと原稿の大幅な改善が推奨されます")
        elif adherence_score < 95.0:
            suggestions.append("細部の調整でより高品質な原稿になります")

        return suggestions

    def _get_generic_suggestions(self, adherence_score: float) -> list[str]:
        """準拠率に基づく一般的な提案

        Args:
            adherence_score: 準拠率

        Returns:
            list[str]: 提案リスト
        """
        if adherence_score >= 95.0:
            return ["✅ 優秀な準拠率です。このまま公開品質を維持してください"]
        if adherence_score >= 80.0:
            return ["⚠️ 細部の調整でより高品質な原稿になります"]
        return ["❌ プロットの見直しと原稿の大幅な改善が推奨されます"]

    def set_temperature(self, temperature: float) -> None:
        """LLM温度設定

        Args:
            temperature: 温度（0.0-1.0）
        """
        if not 0.0 <= temperature <= 1.0:
            msg = "温度は0.0-1.0の範囲である必要があります"
            raise ValueError(msg)
        self._temperature = temperature

    def set_timeout(self, timeout: int) -> None:
        """LLMタイムアウト設定

        Args:
            timeout: タイムアウト（秒）
        """
        if timeout <= 0:
            msg = "タイムアウトは正の数である必要があります"
            raise ValueError(msg)
        self._timeout = timeout
