"""
Git Hooks for GSD-RLM Memory System.

Provides automatic fact extraction from git commits to Memory Bridge,
enabling project-level persistence of development history.

Key components:
- GitFactExtractor: Extracts facts from commit messages and changed files
- install_git_hooks: Installs post-commit hook for automatic extraction
"""

import os
import re
import stat
import subprocess
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from gsd_rlm.memory.bridge.store import MemoryBridge


def _utcnow_iso() -> str:
    """Get current UTC time as ISO string (timezone-aware)."""
    return datetime.now(timezone.utc).isoformat()


@dataclass
class GitCommitInfo:
    """
    Information extracted from a git commit.

    Attributes:
        commit_hash: The commit SHA hash.
        message: The commit message.
        author: Commit author name.
        timestamp: ISO timestamp of commit.
        changed_files: List of files changed in this commit.
    """

    commit_hash: str
    message: str
    author: str = ""
    timestamp: str = field(default_factory=_utcnow_iso)
    changed_files: List[str] = field(default_factory=list)

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "commit_hash": self.commit_hash,
            "message": self.message,
            "author": self.author,
            "timestamp": self.timestamp,
            "changed_files": self.changed_files,
        }


class GitFactExtractor:
    """
    Extracts facts from git commits and stores them in Memory Bridge (MEM-10).

    Automatically extracts structured facts from commit messages and changed
    files, routing them to appropriate Memory Bridge levels.

    Extraction patterns:
    - "Phase X: ..." → BridgeFact at L1_PHASE with phase_id
    - "Fixes #123" → BridgeFact at L2_PROJECT with issue resolution
    - "feat|fix|refactor:" → BridgeFact with commit type
    - Files in src/ → BridgeFact with modified_modules list

    Attributes:
        memory_bridge: MemoryBridge instance for fact storage.
        project_root: Root directory of the git repository.

    Example:
        >>> from gsd_rlm.memory.bridge.store import MemoryBridge
        >>> bridge = MemoryBridge(Path("/path/to/project"))
        >>> extractor = GitFactExtractor(bridge, Path("/path/to/project"))
        >>> facts = extractor.extract_from_commit("abc123")
    """

    # Regex patterns for fact extraction
    PHASE_PATTERN = re.compile(
        r"(?:phase|Phase|PHASE)\s*[:\-]?\s*(\d+(?:\.\d+)?)\s*[:\-]?\s*(.+?)(?:\n|$)",
        re.IGNORECASE,
    )
    ISSUE_PATTERN = re.compile(
        r"(?:fixes|closes|resolves|Fixes|Closes|Resolves)\s*#(\d+)", re.IGNORECASE
    )
    COMMIT_TYPE_PATTERN = re.compile(
        r"^(feat|fix|refactor|docs|test|chore|style|perf|ci|build|revert)"
        r"(\([^)]+\))?:\s*(.+)$",
        re.MULTILINE,
    )
    PLAN_PATTERN = re.compile(
        r"(?:plan|Plan|PLAN)\s*[:\-]?\s*(\d+(?:\.\d+)?-\d+)", re.IGNORECASE
    )

    def __init__(self, memory_bridge: "MemoryBridge", project_root: Path):
        """
        Initialize GitFactExtractor.

        Args:
            memory_bridge: MemoryBridge instance for storing extracted facts.
            project_root: Root directory of the git repository.
        """
        self.memory_bridge = memory_bridge
        self.project_root = Path(project_root)

    def extract_from_commit(self, commit_hash: str) -> List[dict]:
        """
        Extract facts from a git commit.

        Args:
            commit_hash: The commit SHA hash (full or short).

        Returns:
            List of extracted fact dictionaries.
        """
        from gsd_rlm.memory.bridge.facts import BridgeFact, BridgeLevel

        facts: List[dict] = []

        # Get commit information
        message = self._get_commit_message(commit_hash)
        changed_files = self._get_changed_files(commit_hash)
        commit_info = self._get_commit_info(commit_hash)

        # Extract facts from commit message
        message_facts = self._extract_from_message(message, commit_hash)
        facts.extend(message_facts)

        # Extract facts from changed files
        file_facts = self._extract_from_files(changed_files, commit_hash)
        facts.extend(file_facts)

        # Store facts in Memory Bridge
        for fact_data in facts:
            try:
                fact = BridgeFact.create(
                    level=BridgeLevel(fact_data["level"]),
                    scope_id=fact_data["scope_id"],
                    key=fact_data["key"],
                    value=fact_data["value"],
                    source=f"git:{commit_hash[:8]}",
                    confidence=fact_data.get("confidence", 1.0),
                )
                self.memory_bridge.store_fact(fact)
            except (ValueError, KeyError):
                # Skip invalid facts
                continue

        return facts

    def _get_commit_message(self, commit_hash: str) -> str:
        """
        Get commit message for a given commit hash.

        Args:
            commit_hash: The commit SHA hash.

        Returns:
            Commit message as string, or empty string on error.
        """
        try:
            result = subprocess.run(
                ["git", "log", "-1", "--format=%B", commit_hash],
                cwd=self.project_root,
                capture_output=True,
                text=True,
                timeout=30,
            )
            return result.stdout.strip()
        except (subprocess.SubprocessError, OSError):
            return ""

    def _get_changed_files(self, commit_hash: str) -> List[str]:
        """
        Get list of files changed in a commit.

        Args:
            commit_hash: The commit SHA hash.

        Returns:
            List of file paths relative to project root.
        """
        try:
            result = subprocess.run(
                [
                    "git",
                    "diff-tree",
                    "--no-commit-id",
                    "--name-only",
                    "-r",
                    commit_hash,
                ],
                cwd=self.project_root,
                capture_output=True,
                text=True,
                timeout=30,
            )
            return [f.strip() for f in result.stdout.strip().split("\n") if f.strip()]
        except (subprocess.SubprocessError, OSError):
            return []

    def _get_commit_info(self, commit_hash: str) -> GitCommitInfo:
        """
        Get full commit information.

        Args:
            commit_hash: The commit SHA hash.

        Returns:
            GitCommitInfo with commit details.
        """
        try:
            result = subprocess.run(
                ["git", "log", "-1", "--format=%H|%an|%aI", commit_hash],
                cwd=self.project_root,
                capture_output=True,
                text=True,
                timeout=30,
            )
            parts = result.stdout.strip().split("|")
            if len(parts) >= 3:
                return GitCommitInfo(
                    commit_hash=parts[0],
                    message=self._get_commit_message(commit_hash),
                    author=parts[1],
                    timestamp=parts[2],
                    changed_files=self._get_changed_files(commit_hash),
                )
        except (subprocess.SubprocessError, OSError):
            pass

        return GitCommitInfo(
            commit_hash=commit_hash,
            message=self._get_commit_message(commit_hash),
            changed_files=self._get_changed_files(commit_hash),
        )

    def _extract_from_message(self, message: str, commit_hash: str) -> List[dict]:
        """
        Extract facts from commit message.

        Args:
            message: The commit message.
            commit_hash: The commit SHA hash.

        Returns:
            List of fact dictionaries.
        """
        facts: List[dict] = []

        # Extract phase facts
        phase_matches = self.PHASE_PATTERN.findall(message)
        for phase_id, description in phase_matches:
            facts.append(
                {
                    "level": "L1_PHASE",
                    "scope_id": f"{phase_id}-phase",
                    "key": f"commit_{commit_hash[:8]}_phase",
                    "value": {
                        "phase_id": phase_id,
                        "description": description.strip(),
                        "commit": commit_hash,
                    },
                    "confidence": 0.9,
                }
            )

        # Extract issue resolution facts
        issue_matches = self.ISSUE_PATTERN.findall(message)
        for issue_num in issue_matches:
            facts.append(
                {
                    "level": "L2_PROJECT",
                    "scope_id": "project",
                    "key": f"issue_{issue_num}_resolved",
                    "value": {
                        "issue_number": int(issue_num),
                        "commit": commit_hash,
                        "resolved_at": _utcnow_iso(),
                    },
                    "confidence": 1.0,
                }
            )

        # Extract commit type facts
        type_match = self.COMMIT_TYPE_PATTERN.search(message)
        if type_match:
            commit_type = type_match.group(1)
            scope = type_match.group(2) or ""
            description = type_match.group(3)

            facts.append(
                {
                    "level": "L2_PROJECT",
                    "scope_id": "project",
                    "key": f"commit_{commit_hash[:8]}_type",
                    "value": {
                        "type": commit_type,
                        "scope": scope.strip("()"),
                        "description": description.strip(),
                    },
                    "confidence": 1.0,
                }
            )

        # Extract plan references
        plan_matches = self.PLAN_PATTERN.findall(message)
        for plan_id in plan_matches:
            facts.append(
                {
                    "level": "L1_PHASE",
                    "scope_id": f"phase-{plan_id.split('-')[0]}",
                    "key": f"plan_{plan_id}_commit",
                    "value": {
                        "plan_id": plan_id,
                        "commit": commit_hash,
                    },
                    "confidence": 0.95,
                }
            )

        return facts

    def _extract_from_files(self, files: List[str], commit_hash: str) -> List[dict]:
        """
        Extract facts from changed files.

        Args:
            files: List of changed file paths.
            commit_hash: The commit SHA hash.

        Returns:
            List of fact dictionaries.
        """
        facts: List[dict] = []

        if not files:
            return facts

        # Track modules/components affected
        modules: set = set()
        test_files: List[str] = []
        doc_files: List[str] = []
        source_files: List[str] = []

        for filepath in files:
            # Extract module from path
            if filepath.startswith("src/"):
                parts = filepath.split("/")
                if len(parts) >= 2:
                    # Get module name (e.g., "memory", "agents", "session")
                    # parts[0] = "src", parts[1] = module name
                    modules.add(parts[1])
                source_files.append(filepath)
            elif filepath.startswith("tests/"):
                test_files.append(filepath)
            elif filepath.startswith("docs/") or filepath.endswith(".md"):
                doc_files.append(filepath)

        # Create module modification fact
        if modules:
            facts.append(
                {
                    "level": "L2_PROJECT",
                    "scope_id": "project",
                    "key": f"commit_{commit_hash[:8]}_modules",
                    "value": {
                        "modules": sorted(modules),
                        "commit": commit_hash,
                        "file_count": len(files),
                    },
                    "confidence": 1.0,
                }
            )

        # Track test changes
        if test_files:
            facts.append(
                {
                    "level": "L2_PROJECT",
                    "scope_id": "project",
                    "key": f"commit_{commit_hash[:8]}_tests",
                    "value": {
                        "test_files": test_files,
                        "test_count": len(test_files),
                        "commit": commit_hash,
                    },
                    "confidence": 1.0,
                }
            )

        # Track documentation changes
        if doc_files:
            facts.append(
                {
                    "level": "L2_PROJECT",
                    "scope_id": "project",
                    "key": f"commit_{commit_hash[:8]}_docs",
                    "value": {
                        "doc_files": doc_files,
                        "commit": commit_hash,
                    },
                    "confidence": 1.0,
                }
            )

        return facts


def install_git_hooks(project_root: Path, force: bool = False) -> bool:
    """
    Install git post-commit hook for automatic fact extraction.

    Creates a post-commit hook that calls GitFactExtractor on the HEAD commit.
    The hook logs its execution to .gsd-rlm/hooks.log.

    Args:
        project_root: Root directory of the git repository.
        force: If True, overwrite existing hook.

    Returns:
        True if hook was installed successfully, False otherwise.

    Example:
        >>> from pathlib import Path
        >>> success = install_git_hooks(Path("/path/to/project"))
        >>> if success:
        ...     print("Hook installed!")
    """
    git_dir = project_root / ".git"
    hooks_dir = git_dir / "hooks"

    # Check if this is a git repository
    if not git_dir.exists():
        return False

    # Ensure hooks directory exists
    hooks_dir.mkdir(parents=True, exist_ok=True)

    hook_path = hooks_dir / "post-commit"

    # Check if hook already exists
    if hook_path.exists() and not force:
        # Check if it's our hook
        try:
            content = hook_path.read_text(encoding="utf-8")
            if "gsd_rlm.memory.integration.git_hooks" in content:
                return True  # Already installed
        except OSError:
            pass
        return False  # Different hook exists

    # Create hook content
    hook_content = """#!/bin/bash
# GSD-RLM post-commit hook for automatic fact extraction
# Auto-generated by install_git_hooks()

LOG_FILE="{log_file}"
EXTRACTOR_CMD="from gsd_rlm.memory.integration.git_hooks import run_extraction; run_extraction()"

# Log execution
echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ") - Running GSD-RLM fact extraction" >> "$LOG_FILE"

# Run extraction
python -c "$EXTRACTOR_CMD" 2>&1 >> "$LOG_FILE"

exit 0
""".format(log_file=str(project_root / ".gsd-rlm" / "hooks.log"))

    try:
        # Write hook file
        hook_path.write_text(hook_content, encoding="utf-8")

        # Make executable (Unix)
        if os.name != "nt":  # Not Windows
            current_mode = hook_path.stat().st_mode
            hook_path.chmod(current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)

        # Ensure .gsd-rlm directory exists for logs
        gsd_dir = project_root / ".gsd-rlm"
        gsd_dir.mkdir(parents=True, exist_ok=True)

        return True
    except OSError:
        return False


def run_extraction(project_root: Optional[Path] = None) -> List[dict]:
    """
    Run fact extraction on the HEAD commit.

    This function is called by the post-commit hook to extract facts
    from the most recent commit.

    Args:
        project_root: Root directory of the project. If None, uses current directory.

    Returns:
        List of extracted facts.

    Example:
        >>> facts = run_extraction(Path("/path/to/project"))
        >>> print(f"Extracted {len(facts)} facts")
    """
    from gsd_rlm.memory.bridge.store import MemoryBridge

    # Determine project root
    if project_root is None:
        project_root = Path.cwd()

    project_root = Path(project_root)

    try:
        # Get HEAD commit hash
        result = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            cwd=project_root,
            capture_output=True,
            text=True,
            timeout=30,
        )
        head_hash = result.stdout.strip()

        if not head_hash:
            return []

        # Initialize MemoryBridge
        memory_bridge = MemoryBridge(project_root)

        # Initialize extractor
        extractor = GitFactExtractor(memory_bridge, project_root)

        # Extract facts from HEAD
        facts = extractor.extract_from_commit(head_hash)

        return facts
    except (subprocess.SubprocessError, OSError, ValueError):
        return []


def uninstall_git_hooks(project_root: Path) -> bool:
    """
    Remove the GSD-RLM post-commit hook.

    Args:
        project_root: Root directory of the git repository.

    Returns:
        True if hook was removed, False if not found or error.
    """
    hook_path = project_root / ".git" / "hooks" / "post-commit"

    if not hook_path.exists():
        return False

    try:
        # Check if it's our hook
        content = hook_path.read_text(encoding="utf-8")
        if "gsd_rlm.memory.integration.git_hooks" in content:
            hook_path.unlink()
            return True
        return False  # Different hook, don't remove
    except OSError:
        return False
