"""
Commit message validation and traceability extraction.

This module provides functionality for validating commit messages
against the traceability format and extracting traceability information
from commit history.

Format: type(phase-plan-task): description
Example: feat(05-01-03): add checkpoint pause/resume logic

Requirements: INT-14 (atomic commits), INT-15 (traceability)
"""

import re
import subprocess
from dataclasses import dataclass
from typing import Optional

from gsd_rlm.git.commits import _run_git_command


# Compiled regex pattern for commit message validation
# Matches: type(phase-plan-task): description
# - type: feat|fix|test|docs|refactor|style|chore
# - phase: digits with optional version (e.g., 05 or 05.1)
# - plan: digits
# - task: digits
COMMIT_PATTERN = re.compile(
    r"^(feat|fix|test|docs|refactor|style|chore)\((\d+(?:\.\d+)?)-(\d+)-(\d+)\): .+$"
)


@dataclass
class TraceabilityInfo:
    """Traceability information extracted from a commit message.

    Attributes:
        commit_type: The type of commit (feat, fix, test, etc.)
        phase: Phase number (e.g., "05")
        plan: Plan number (e.g., "01")
        task: Task number (e.g., "03")
        description: Brief description from commit message
        commit_hash: Git commit hash
    """

    commit_type: str
    phase: str
    plan: str
    task: str
    description: str
    commit_hash: str

    def to_dict(self) -> dict:
        """Serialize to dictionary for logging or reporting."""
        return {
            "commit_type": self.commit_type,
            "phase": self.phase,
            "plan": self.plan,
            "task": self.task,
            "description": self.description,
            "commit_hash": self.commit_hash,
        }

    @property
    def trace_id(self) -> str:
        """Return the traceability ID (phase-plan-task)."""
        return f"{self.phase}-{self.plan}-{self.task}"


def validate_commit_message(message: str) -> bool:
    """Validate that a commit message follows the traceability format.

    Args:
        message: The commit message to validate (first line only)

    Returns:
        True if the message matches the pattern, False otherwise

    Examples:
        >>> validate_commit_message("feat(05-01-03): add feature")
        True
        >>> validate_commit_message("fix(04-02-01): fix bug")
        True
        >>> validate_commit_message("invalid commit message")
        False
    """
    # Only check the first line (subject line)
    first_line = message.split("\n")[0].strip()
    return bool(COMMIT_PATTERN.match(first_line))


def extract_traceability(
    message: str, commit_hash: str = ""
) -> Optional[TraceabilityInfo]:
    """Extract traceability information from a commit message.

    Args:
        message: The commit message to parse
        commit_hash: Optional commit hash to include

    Returns:
        TraceabilityInfo if message is valid, None otherwise

    Examples:
        >>> info = extract_traceability("feat(05-01-03): add feature", "abc1234")
        >>> info.phase
        '05'
        >>> info.plan
        '01'
        >>> info.task
        '03'
    """
    first_line = message.split("\n")[0].strip()
    match = COMMIT_PATTERN.match(first_line)

    if not match:
        return None

    commit_type, phase, plan, task = match.groups()

    # Extract description (everything after ": ")
    description = first_line.split(": ", 1)[1] if ": " in first_line else ""

    return TraceabilityInfo(
        commit_type=commit_type,
        phase=phase,
        plan=plan,
        task=task,
        description=description,
        commit_hash=commit_hash,
    )


def get_commit_history(
    working_dir: str = ".",
    phase: Optional[str] = None,
    limit: int = 100,
) -> list[TraceabilityInfo]:
    """Get commit history with traceability information.

    Parses git log and extracts traceability info from commit messages.

    Args:
        working_dir: Directory to run git commands in
        phase: Optional phase filter (e.g., "05" for phase 5)
        limit: Maximum number of commits to retrieve

    Returns:
        List of TraceabilityInfo for commits matching the pattern
    """
    # Get commit log with hash and subject
    result = _run_git_command(
        ["log", f"-{limit}", "--format=%h %s"],
        working_dir=working_dir,
        check=False,
    )

    if result.returncode != 0:
        return []

    commits: list[TraceabilityInfo] = []
    for line in result.stdout.strip().split("\n"):
        if not line.strip():
            continue

        # Parse "hash subject" format
        parts = line.strip().split(" ", 1)
        if len(parts) < 2:
            continue

        commit_hash, subject = parts
        info = extract_traceability(subject, commit_hash)

        if info:
            # Filter by phase if specified
            if phase is None or info.phase == phase:
                commits.append(info)

    return commits


def get_commits_by_plan(
    phase: str,
    plan: str,
    working_dir: str = ".",
) -> list[TraceabilityInfo]:
    """Get all commits for a specific phase and plan.

    Args:
        working_dir: Directory to run git commands in
        phase: Phase number (e.g., "05")
        plan: Plan number (e.g., "01")

    Returns:
        List of TraceabilityInfo for commits in the specified plan
    """
    history = get_commit_history(working_dir, phase=phase)
    return [info for info in history if info.plan == plan]


def get_commits_by_task(
    phase: str,
    plan: str,
    task: str,
    working_dir: str = ".",
) -> list[TraceabilityInfo]:
    """Get all commits for a specific task.

    Args:
        working_dir: Directory to run git commands in
        phase: Phase number (e.g., "05")
        plan: Plan number (e.g., "01")
        task: Task number (e.g., "03")

    Returns:
        List of TraceabilityInfo for the specified task
    """
    history = get_commit_history(working_dir, phase=phase)
    return [info for info in history if info.plan == plan and info.task == task]


def generate_commit_report(
    phase: str,
    working_dir: str = ".",
    title: Optional[str] = None,
) -> str:
    """Generate a markdown report of commits for a phase.

    Groups commits by plan and formats as a markdown report.

    Args:
        phase: Phase number to report on
        working_dir: Directory to run git commands in
        title: Optional custom title for the report

    Returns:
        Markdown formatted report string
    """
    commits = get_commit_history(working_dir, phase=phase)

    if not commits:
        return f"# Phase {phase} Commit Report\n\nNo traceable commits found.\n"

    # Group commits by plan
    plans: dict[str, list[TraceabilityInfo]] = {}
    for info in commits:
        if info.plan not in plans:
            plans[info.plan] = []
        plans[info.plan].append(info)

    # Sort plans numerically
    sorted_plans = sorted(plans.keys(), key=lambda x: int(x))

    # Build report
    report_title = title or f"Phase {phase} Commit Report"
    lines = [
        f"# {report_title}",
        "",
        f"**Total Commits:** {len(commits)}",
        f"**Plans:** {len(plans)}",
        "",
    ]

    for plan in sorted_plans:
        plan_commits = plans[plan]
        lines.append(f"## Plan {phase}-{plan}")
        lines.append("")
        lines.append(f"**Commits:** {len(plan_commits)}")
        lines.append("")
        lines.append("| Task | Type | Description | Hash |")
        lines.append("|------|------|-------------|------|")

        # Sort by task number
        sorted_commits = sorted(plan_commits, key=lambda x: int(x.task))
        for info in sorted_commits:
            lines.append(
                f"| {info.task} | {info.commit_type} | {info.description} | {info.commit_hash} |"
            )

        lines.append("")

    return "\n".join(lines)


def get_phase_summary(phase: str, working_dir: str = ".") -> dict:
    """Get summary statistics for a phase.

    Args:
        working_dir: Directory to run git commands in
        phase: Phase number

    Returns:
        Dictionary with summary statistics
    """
    commits = get_commit_history(working_dir, phase=phase)

    if not commits:
        return {
            "phase": phase,
            "total_commits": 0,
            "plans": [],
            "commits_by_type": {},
            "commits_by_plan": {},
        }

    # Count by type
    by_type: dict[str, int] = {}
    for info in commits:
        by_type[info.commit_type] = by_type.get(info.commit_type, 0) + 1

    # Count by plan
    by_plan: dict[str, int] = {}
    for info in commits:
        by_plan[info.plan] = by_plan.get(info.plan, 0) + 1

    return {
        "phase": phase,
        "total_commits": len(commits),
        "plans": sorted(by_plan.keys(), key=lambda x: int(x)),
        "commits_by_type": by_type,
        "commits_by_plan": by_plan,
    }
