#!/usr/bin/env python3
"""Phase 5a: コア基盤層(DomainCommand, AggregateRoot, DomainEvent)の@abstractmethod削除スクリプト

CQRS/DDD基盤となるコアクラスをABCからProtocolに変換
- DomainCommand (commands/base.py): @abstractmethodなし、ABC使用のみ
- AggregateRoot (entities/base.py): @abstractmethodなし、ABC使用のみ
- DomainEvent (events/base.py): @abstractmethodなし、ABC使用のみ

各クラスはABC継承のみで、具体的な@abstractmethodは定義されていない
Protocol化により、構造的部分型によるインターフェース定義に統一化

Usage:
    python scripts/migrate/fix_core_base_classes.py [--dry-run]
    python scripts/migrate/fix_core_base_classes.py --apply

Options:
    --dry-run       ドライランモード（デフォルト）
    --apply         ファイルに変更を適用
"""

import re
import sys
from pathlib import Path
from typing import NamedTuple


class FileProcessResult(NamedTuple):
    """ファイル処理結果"""
    file: str
    abc_import_removed: bool
    abc_changed: bool
    protocol_import_added: bool
    modified: bool
    error: str | None = None


def remove_abc_import(content: str) -> tuple[str, bool]:
    """ABC import を削除"""
    if re.search(r"from abc import.*\n", content):
        content = re.sub(r"from abc import [^\n]*\n", "", content)
        return content, True
    if "import abc\n" in content:
        content = content.replace("import abc\n", "")
        return content, True
    return content, False


def change_abc_to_protocol(content: str) -> tuple[str, bool]:
    """ABC を Protocol に変更"""
    original = content

    # パターン1: class X(ABC):
    if re.search(r"class\s+\w+\s*\(\s*ABC\s*\):", content):
        content = re.sub(r"class\s+(\w+)\s*\(\s*ABC\s*\):", r"class \1(Protocol):", content)

    # パターン2: class X(ABC, ...) または class X(X, ABC)
    if re.search(r"class\s+\w+\s*\([^)]*ABC[^)]*\)", content):
        content = re.sub(r"(\w+)\s*\(\s*ABC\s*,", r"\1(Protocol, ", content)
        content = re.sub(r",\s*ABC\s*\)", ", Protocol)", content)

    return content, content != original


def add_protocol_import(content: str) -> tuple[str, bool]:
    """Protocol import を追加"""
    # 既にProtocol importがある場合はスキップ
    if re.search(r"from typing import.*Protocol", content, re.DOTALL):
        return content, False

    # Protocol定義が含まれている場合のみ追加
    if "Protocol)" not in content:
        return content, False

    # typing importを探して追加
    if re.search(r"from typing import", content):
        # 既存のtyping importをProtocol追加で更新
        pattern = r"(from typing import )([^\n]*)"
        def add_protocol(m):
            imports = m.group(2)
            if "Protocol" in imports:
                return m.group(0)
            # Protocolを先頭に追加
            return f"{m.group(1)}Protocol, {imports}"

        new_content = re.sub(pattern, add_protocol, content, count=1)
        if new_content != content:
            return new_content, True
    else:
        # typing importがない場合は先頭に追加
        new_content = "from typing import Protocol\n" + content
        return new_content, True

    return content, False


def process_file(file_path: Path, dry_run: bool = True) -> FileProcessResult:
    """単一ファイルを処理"""
    try:
        content = file_path.read_text(encoding="utf-8")
    except Exception as e:
        return FileProcessResult(
            file=str(file_path),
            abc_import_removed=False,
            abc_changed=False,
            protocol_import_added=False,
            modified=False,
            error=str(e),
        )

    original_content = content

    # Step 1: ABC importを削除
    content, abc_import_removed = remove_abc_import(content)

    # Step 2: ABC -> Protocol に変更
    content, abc_changed = change_abc_to_protocol(content)

    # Step 3: Protocol importを追加
    content, protocol_import_added = add_protocol_import(content)

    # 変更をファイルに適用
    modified = content != original_content
    if modified and not dry_run:
        try:
            file_path.write_text(content, encoding="utf-8")
        except Exception as e:
            return FileProcessResult(
                file=str(file_path),
                abc_import_removed=abc_import_removed,
                abc_changed=abc_changed,
                protocol_import_added=protocol_import_added,
                modified=False,
                error=f"Write error: {e!s}",
            )

    return FileProcessResult(
        file=str(file_path),
        abc_import_removed=abc_import_removed,
        abc_changed=abc_changed,
        protocol_import_added=protocol_import_added,
        modified=modified,
        error=None,
    )


def get_target_files() -> list[Path]:
    """Phase 5a 対象ファイルを取得"""
    base_path = Path(__file__).parent.parent.parent / "src" / "noveler" / "domain"

    return [
        base_path / "commands" / "base.py",
        base_path / "entities" / "base.py",
        base_path / "events" / "base.py",
    ]


def main():
    """メイン処理"""
    dry_run = "--apply" not in sys.argv

    target_files = get_target_files()
    results = []

    print("=" * 70)
    print("Phase 5a: コア基盤層(DomainCommand/AggregateRoot/DomainEvent)の@abstractmethod削除")
    print("=" * 70)
    print(f"Mode: {'DRY-RUN' if dry_run else 'APPLY'}")
    print(f"Files to process: {len(target_files)}")
    print()

    for file_path in target_files:
        if not file_path.exists():
            print(f"⚠️  File not found: {file_path}")
            continue

        result = process_file(file_path, dry_run=dry_run)
        results.append(result)

        if result.error:
            print(f"❌ Error: {file_path.name}")
            print(f"   {result.error}")
        elif result.modified:
            print(f"✅ Modified: {file_path.name}")
            if result.abc_import_removed:
                print("   - ABC import removed")
            if result.abc_changed:
                print("   - ABC -> Protocol: 1")
            if result.protocol_import_added:
                print("   - Protocol import added")
        else:
            print(f"⊘ No changes: {file_path.name}")

    # サマリー
    print()
    print("=" * 70)
    print("Summary")
    print("=" * 70)

    abc_removed_count = sum(1 for r in results if r.abc_import_removed)
    abc_changed_count = sum(1 for r in results if r.abc_changed)
    protocol_added_count = sum(1 for r in results if r.protocol_import_added)
    modified_count = sum(1 for r in results if r.modified)
    error_count = sum(1 for r in results if r.error)

    print(f"ABC import removed: {abc_removed_count}")
    print(f"ABC -> Protocol changed: {abc_changed_count}")
    print(f"Protocol import added: {protocol_added_count}")
    print(f"Modified files: {modified_count}/{len(target_files)}")
    print(f"Errors: {error_count}")

    if error_count == 0:
        if dry_run:
            print("\n✅ Dry-run completed successfully.")
            print("Run with --apply flag to apply changes.")
        else:
            print("\n✅ Changes applied.")
    else:
        print("\n❌ Some errors occurred.")
        sys.exit(1)


if __name__ == "__main__":
    main()
