#!/usr/bin/env python3
"""Check for CLAUDE.md compliant module headers in Python files.

Purpose:
    Verify that all Python files start with proper headers following
    Documentation Standardization Guide: File/Purpose/Context format.

Context:
    Part of CI pipeline. Can be run locally via `python scripts/ci/check_module_headers.py`.
    Integrates with Makefile (make check-docs) and GitHub Actions (pr-check.yml).

Side Effects:
    Outputs violations to stdout. Returns non-zero exit code on failures.
"""

from __future__ import annotations

import re
import sys
from dataclasses import dataclass
from pathlib import Path


@dataclass
class HeaderViolation:
    """Module header violation record.

    Purpose:
        Represent a single missing or malformed module header.

    Attributes:
        file_path: Path to the file with the violation.
        issue: Description of the violation.
    """

    file_path: Path
    issue: str


HEADER_PATTERN = re.compile(
    r"^#\s+File:\s+\S+\s*\n#\s+Purpose:\s+\S+.*\n#\s+Context:\s+\S+.*",
    re.MULTILINE,
)


def read_file_header(file_path: Path, lines: int = 10) -> str:
    """Read the first N lines of a file.

    Purpose:
        Extract file header for validation.

    Args:
        file_path: Path to the file.
        lines: Number of lines to read (default: 10).

    Returns:
        Header text or empty string if file cannot be read.

    Side Effects:
        None.
    """
    try:
        with open(file_path, encoding="utf-8") as f:
            return "".join(f.readline() for _ in range(lines))
    except (UnicodeDecodeError, OSError):
        return ""


def check_header_format(header: str, file_path: Path) -> HeaderViolation | None:
    """Check if header follows CLAUDE.md format.

    Purpose:
        Validate that header contains File, Purpose, and Context lines.

    Args:
        header: Header text to check.
        file_path: Path to the file (for reporting).

    Returns:
        HeaderViolation if format is invalid, None if valid.

    Side Effects:
        None.
    """
    # Skip shebang and docstring
    lines = header.split("\n")
    first_code_line = None
    for i, line in enumerate(lines):
        if line.startswith("#"):
            first_code_line = i
            break

    if first_code_line is None:
        return HeaderViolation(
            file_path=file_path,
            issue="No comment header found (expected File/Purpose/Context)",
        )

    # Extract header lines
    header_lines = []
    for line in lines[first_code_line:]:
        if line.startswith("#"):
            header_lines.append(line)
        else:
            break

    if not header_lines:
        return HeaderViolation(
            file_path=file_path,
            issue="No File/Purpose/Context header found",
        )

    header_text = "\n".join(header_lines)

    # Check for required fields
    has_file = any(line.startswith("# File:") for line in header_lines)
    has_purpose = any(line.startswith("# Purpose:") for line in header_lines)
    has_context = any(line.startswith("# Context:") for line in header_lines)

    missing = []
    if not has_file:
        missing.append("File")
    if not has_purpose:
        missing.append("Purpose")
    if not has_context:
        missing.append("Context")

    if missing:
        return HeaderViolation(
            file_path=file_path,
            issue=f"Missing header fields: {', '.join(missing)}",
        )

    # Validate that fields are not empty
    for line in header_lines:
        if line.startswith("# File:") and len(line.strip()) <= 7:
            return HeaderViolation(file_path=file_path, issue="File path is empty")
        if line.startswith("# Purpose:") and len(line.strip()) <= 10:
            return HeaderViolation(file_path=file_path, issue="Purpose is empty")
        if line.startswith("# Context:") and len(line.strip()) <= 10:
            return HeaderViolation(file_path=file_path, issue="Context is empty")

    return None


def check_file(file_path: Path) -> HeaderViolation | None:
    """Check a single Python file for header compliance.

    Purpose:
        Validate that a Python file has proper CLAUDE.md header.

    Args:
        file_path: Path to Python file to check.

    Returns:
        HeaderViolation if non-compliant, None if valid.

    Side Effects:
        None.
    """
    header = read_file_header(file_path, lines=15)
    if not header:
        return HeaderViolation(
            file_path=file_path,
            issue="Cannot read file",
        )

    return check_header_format(header, file_path)


def check_directory(
    directory: Path,
    exclude_patterns: list[str] | None = None,
) -> list[HeaderViolation]:
    """Check all Python files in a directory for header compliance.

    Purpose:
        Recursively find and check all Python files.

    Args:
        directory: Root directory to check.
        exclude_patterns: Glob patterns to exclude (default: None).

    Returns:
        List of all HeaderViolation instances found.

    Side Effects:
        None.
    """
    if exclude_patterns is None:
        exclude_patterns = [
            "**/__pycache__/**",
            "**/.git/**",
            "**/.*/**",
            "**/.venv/**",
            "**/venv/**",
        ]

    violations: list[HeaderViolation] = []

    for py_file in directory.rglob("*.py"):
        # Skip excluded patterns
        if any(py_file.match(pattern) for pattern in exclude_patterns):
            continue

        violation = check_file(py_file)
        if violation:
            violations.append(violation)

    return violations


def format_violation(violation: HeaderViolation) -> str:
    """Format a violation for display.

    Purpose:
        Create human-readable violation message.

    Args:
        violation: HeaderViolation instance.

    Returns:
        Formatted violation string.

    Side Effects:
        None.
    """
    return f"{violation.file_path}: {violation.issue}"


def main() -> int:
    """Main entry point.

    Purpose:
        Check source code for CLAUDE.md compliant headers.

    Returns:
        0 if no violations, 1 if violations found.

    Side Effects:
        Prints violations to stdout/stderr.
    """
    # Check src/ directory (scripts are more flexible)
    check_dirs = [Path("src")]

    all_violations: list[HeaderViolation] = []
    for check_dir in check_dirs:
        if check_dir.exists():
            print(f"🔍 Checking module headers in {check_dir}/...")
            violations = check_directory(
                check_dir,
                exclude_patterns=[
                    "**/__pycache__/**",
                    "**/.git/**",
                    "**/.*/**",
                    "**/.venv/**",
                    "**/venv/**",
                    "**/migrations/**",
                    "**/test_*.py",
                    "**/conftest.py",
                    "**/__init__.py",  # Optional for __init__.py
                ],
            )
            all_violations.extend(violations)

    if all_violations:
        print(f"\n❌ Found {len(all_violations)} header violations:\n")
        for violation in sorted(all_violations, key=lambda v: str(v.file_path)):
            print(f"  {format_violation(violation)}")
        print(
            "\n⚠️  Documentation Standard: See docs/guides/documentation_standardization_guide.md#2-claudemd形式の標準化"
        )
        return 1

    print("✅ All module headers follow CLAUDE.md format!")
    return 0


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