#!/usr/bin/env python3
"""Protocol設計ガイドライン検証スクリプト

File: scripts/quality_gates/protocol_validator.py
Purpose: Protocol定義がアンチパターンを含まないか検証し、移行優先度を提示
Context: noveler型注釈パターン再設計における自動検証ツール
"""

import argparse
import sys
from collections import defaultdict
from pathlib import Path
from typing import NamedTuple


class ValidationIssue(NamedTuple):
    """検証結果の問題"""
    severity: str  # "CRITICAL", "WARNING", "INFO"
    file_path: str
    line_number: int
    message: str
    fix_suggestion: str


class ProtocolValidator:
    """Protocol設計ガイドライン検証器"""

    def __init__(self, root_path: Path) -> None:
        """初期化

        Args:
            root_path: 検証対象ディレクトリ
        """
        self.root_path = root_path
        self.issues: list[ValidationIssue] = []

    def validate(self) -> None:
        """全Pythonファイルを検証"""
        python_files = self.root_path.rglob("*.py")

        for file_path in python_files:
            self._validate_file(file_path)

    def _validate_file(self, file_path: Path) -> None:
        """単一ファイルを検証

        Args:
            file_path: 検証対象ファイル
        """
        try:
            content = file_path.read_text(encoding="utf-8")
            lines = content.split("\n")
        except (UnicodeDecodeError, OSError):
            return

        # Protocol定義を検出
        if "from typing import Protocol" not in content and "Protocol" not in content:
            return

        for line_num, line in enumerate(lines, 1):
            # Check 1: @abstractmethod in Protocol
            if self._is_protocol_definition(lines, line_num):
                self._check_abstractmethod_in_protocol(
                    file_path, lines, line_num
                )

            # Check 2: @property + Protocol
            if "class" in line and "Protocol" in line:
                self._check_property_implementation(file_path, lines, line_num)

            # Check 3: Complex logic in Protocol
            self._check_protocol_logic(file_path, lines, line_num)

        # Check 4: runtime_checkable usage
        self._check_runtime_checkable(file_path, lines)

    def _is_protocol_definition(self, lines: list[str], current_line: int) -> bool:
        """現在行がProtocol定義内かどうかを判定

        Args:
            lines: ファイル行リスト
            current_line: 現在行番号（1-indexed）

        Returns:
            True if inside Protocol definition
        """
        # 過去50行を遡ってProtocol定義を検索
        start = max(0, current_line - 50)
        search_text = "\n".join(lines[start : current_line])

        return "class" in search_text and "Protocol" in search_text

    def _check_abstractmethod_in_protocol(
        self, file_path: Path, lines: list[str], line_num: int
    ) -> None:
        """Protocol内の@abstractmethod使用を検出

        Args:
            file_path: ファイルパス
            lines: ファイル行リスト
            line_num: 行番号（1-indexed）
        """
        if "@abstractmethod" not in lines[line_num - 1]:
            return

        # 直前50行でProtocol定義を確認
        start = max(0, line_num - 50)
        preceding = "\n".join(lines[start : line_num - 1])

        if "class" not in preceding or "Protocol" not in preceding:
            return

        self.issues.append(
            ValidationIssue(
                severity="CRITICAL",
                file_path=str(file_path),
                line_number=line_num,
                message="Protocol定義内に@abstractmethodが存在（アンチパターン）",
                fix_suggestion="@abstractmethodを削除。Protocolは構造的部分型なので不要",
            )
        )

    def _check_property_implementation(
        self, file_path: Path, lines: list[str], line_num: int
    ) -> None:
        """Protocol内のプロパティ実装を検出

        Args:
            file_path: ファイルパス
            lines: ファイル行リスト
            line_num: 行番号
        """
        # 過去20行でProtocol定義を確認
        start = max(0, line_num - 20)
        is_protocol = any("Protocol" in l for l in lines[start : line_num - 1])

        if not is_protocol:
            return

        # @propertyデコレータ後の実装を確認
        for i in range(line_num, min(line_num + 10, len(lines))):
            line = lines[i]
            if line.strip().startswith("return "):
                self.issues.append(
                    ValidationIssue(
                        severity="WARNING",
                        file_path=str(file_path),
                        line_number=i + 1,
                        message="Protocol内に実装が含まれている",
                        fix_suggestion="Protocolは...（エリプシス）のみで、実装は含めない",
                    )
                )
                break

    def _check_protocol_logic(
        self, file_path: Path, lines: list[str], line_num: int
    ) -> None:
        """Protocol定義内の複雑なロジック検出

        Args:
            file_path: ファイルパス
            lines: ファイル行リスト
            line_num: 行番号
        """
        if not any(kw in lines[line_num - 1] for kw in ["if ", "for ", "while "]):
            return

        # 直前50行でProtocol定義を確認
        start = max(0, line_num - 50)
        is_protocol = any("Protocol" in l for l in lines[start : line_num - 1])

        if is_protocol:
            self.issues.append(
                ValidationIssue(
                    severity="WARNING",
                    file_path=str(file_path),
                    line_number=line_num,
                    message="Protocol定義内に制御フロー（if/for/while）が存在",
                    fix_suggestion="Protocolは契約のみ。ロジックは実装クラスで",
                )
            )

    def _check_runtime_checkable(
        self, file_path: Path, lines: list[str]
    ) -> None:
        """@runtime_checkable使用の検証

        Args:
            file_path: ファイルパス
            lines: ファイル行リスト
        """
        content = "\n".join(lines)

        # @runtime_checkable使用箇所をカウント
        runtime_count = content.count("@runtime_checkable")

        if runtime_count > 0:
            # 全体に対する比率を確認
            protocol_count = content.count("class ") and content.count("Protocol")

            if runtime_count > 3:  # 過度な使用
                self.issues.append(
                    ValidationIssue(
                        severity="INFO",
                        file_path=str(file_path),
                        line_number=1,
                        message=f"@runtime_checkableが{runtime_count}回使用されている（過度な可能性）",
                        fix_suggestion="ランタイムチェック（isinstance）が本当に必要か確認。コンパイル時型チェック（mypy）で十分な場合が多い",
                    )
                )

    def report(self, export_csv: str | None = None) -> None:
        """検証結果をレポート出力

        Args:
            export_csv: CSV出力先パス（オプション）
        """
        if not self.issues:
            print("✅ Protocol検証: 問題なし")
            return

        print("\n" + "=" * 70)
        print("Protocol設計ガイドライン検証レポート")
        print("=" * 70)

        # 重要度別に分類
        issues_by_severity = defaultdict(list)
        for issue in self.issues:
            issues_by_severity[issue.severity].append(issue)

        # 重大度順に出力
        for severity in ["CRITICAL", "WARNING", "INFO"]:
            if severity not in issues_by_severity:
                continue

            issues = issues_by_severity[severity]
            emoji = {"CRITICAL": "🔴", "WARNING": "🟡", "INFO": "🔵"}[severity]

            print(f"\n{emoji} {severity} ({len(issues)}件)")
            print("-" * 70)

            for issue in issues:
                print(f"  ファイル: {issue.file_path}:{issue.line_number}")
                print(f"  問題: {issue.message}")
                print(f"  対策: {issue.fix_suggestion}")
                print()

        # サマリー
        total = len(self.issues)
        critical = len(issues_by_severity.get("CRITICAL", []))
        warning = len(issues_by_severity.get("WARNING", []))
        info = len(issues_by_severity.get("INFO", []))

        print("=" * 70)
        print(f"合計: {total}件 (🔴 {critical} 🟡 {warning} 🔵 {info})")
        print("=" * 70)

        # CSV出力
        if export_csv:
            self._export_csv(export_csv)

    def _export_csv(self, export_path: str) -> None:
        """検証結果をCSVに出力

        Args:
            export_path: CSV出力先パス
        """
        csv_path = Path(export_path)
        csv_path.parent.mkdir(parents=True, exist_ok=True)

        with csv_path.open("w", encoding="utf-8") as f:
            # ヘッダ
            f.write("severity,file_path,line_number,message,fix_suggestion\n")

            # データ行
            for issue in self.issues:
                f.write(
                    f'"{issue.severity}","{issue.file_path}",'
                    f'{issue.line_number},"{issue.message}","{issue.fix_suggestion}"\n'
                )

        print(f"\n✅ CSV出力: {csv_path}")


def main() -> int:
    """メインエントリーポイント"""
    parser = argparse.ArgumentParser(
        description="Protocol設計ガイドライン検証スクリプト"
    )
    parser.add_argument(
        "path",
        type=Path,
        nargs="?",
        default=Path("src/noveler/domain"),
        help="検証対象ディレクトリ（デフォルト: src/noveler/domain）",
    )
    parser.add_argument(
        "--export",
        type=str,
        help="CSV出力先パス",
    )

    args = parser.parse_args()

    if not args.path.exists():
        print(f"❌ エラー: {args.path} が見つかりません")
        return 1

    validator = ProtocolValidator(args.path)
    validator.validate()
    validator.report(export_csv=args.export)

    return 1 if validator.issues else 0


if __name__ == "__main__":
    sys.exit(main())
