"""Automatic git commit management.

This module provides automatic commit message generation and git operations
for IEUM's AI-assisted development workflow.
"""

from __future__ import annotations

import subprocess
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Optional, TYPE_CHECKING

from ieum.git.diff_analyzer import (
    DiffAnalyzer,
    ChangeAnalysis,
    ChangeCategory,
    get_status,
)

if TYPE_CHECKING:
    from langchain_core.language_models import BaseChatModel


@dataclass
class CommitResult:
    """Result of a commit operation."""

    success: bool
    commit_hash: Optional[str] = None
    message: str = ""
    error: Optional[str] = None
    files_committed: list[str] = field(default_factory=list)
    timestamp: Optional[datetime] = None

    def __str__(self) -> str:
        if self.success:
            return f"Committed {self.commit_hash[:7]}: {self.message}"
        return f"Commit failed: {self.error}"


class AutoCommitManager:
    """Manages automatic git commits with AI-generated messages."""

    # Commit message templates by category
    MESSAGE_TEMPLATES = {
        ChangeCategory.FEATURE: "feat{scope}: {summary}",
        ChangeCategory.FIX: "fix{scope}: {summary}",
        ChangeCategory.REFACTOR: "refactor{scope}: {summary}",
        ChangeCategory.DOCS: "docs{scope}: {summary}",
        ChangeCategory.STYLE: "style{scope}: {summary}",
        ChangeCategory.TEST: "test{scope}: {summary}",
        ChangeCategory.CHORE: "chore{scope}: {summary}",
        ChangeCategory.PERF: "perf{scope}: {summary}",
        ChangeCategory.BUILD: "build{scope}: {summary}",
        ChangeCategory.CI: "ci{scope}: {summary}",
    }

    def __init__(
        self,
        repo_path: str | Path | None = None,
        model: Optional["BaseChatModel"] = None,
    ):
        """Initialize the auto-commit manager.

        Args:
            repo_path: Path to the git repository. Defaults to current directory.
            model: Optional LLM model for enhanced commit message generation.
        """
        self.repo_path = Path(repo_path) if repo_path else Path.cwd()
        self.model = model
        self.analyzer = DiffAnalyzer(self.repo_path)

    def get_status(self) -> dict:
        """Get current repository status.

        Returns:
            Dictionary with branch, staged, unstaged, and untracked files.
        """
        return get_status()

    def analyze_changes(self, staged_only: bool = True) -> ChangeAnalysis:
        """Analyze current changes for commit.

        Args:
            staged_only: If True, only analyze staged changes.

        Returns:
            ChangeAnalysis with categorization and summary.
        """
        if staged_only:
            diff = self.analyzer.get_staged_diff()
        else:
            diff = self.analyzer.get_all_diff()

        return self.analyzer.analyze(diff)

    def generate_commit_message(
        self,
        analysis: Optional[ChangeAnalysis] = None,
        use_ai: bool = False,
    ) -> str:
        """Generate a commit message based on changes.

        Args:
            analysis: Pre-computed change analysis. If None, analyzes staged changes.
            use_ai: If True and model is available, use AI for enhanced message.

        Returns:
            Generated commit message.
        """
        if analysis is None:
            analysis = self.analyze_changes()

        if not analysis.files:
            return "chore: empty commit"

        # Basic template-based message
        scope = f"({analysis.scope})" if analysis.scope else ""
        template = self.MESSAGE_TEMPLATES.get(
            analysis.category, self.MESSAGE_TEMPLATES[ChangeCategory.CHORE]
        )
        message = template.format(scope=scope, summary=analysis.summary)

        # Use AI for enhanced message if available
        if use_ai and self.model:
            message = self._generate_ai_message(analysis, message)

        return message

    def _generate_ai_message(
        self, analysis: ChangeAnalysis, fallback: str
    ) -> str:
        """Generate commit message using AI.

        Args:
            analysis: Change analysis results.
            fallback: Fallback message if AI fails.

        Returns:
            AI-generated or fallback commit message.
        """
        if not self.model:
            return fallback

        try:
            # Build context for AI
            file_summaries = []
            for f in analysis.files[:10]:  # Limit to 10 files
                file_summaries.append(
                    f"- {f.file_path}: +{f.additions}/-{f.deletions} ({f.change_type.value})"
                )

            prompt = f"""Generate a concise git commit message for these changes.

Category: {analysis.category.value}
Scope: {analysis.scope or 'general'}
Files changed:
{chr(10).join(file_summaries)}

Total: +{analysis.total_additions}/-{analysis.total_deletions} in {analysis.total_files} files

Requirements:
1. Follow conventional commits format: type(scope): description
2. Keep the first line under 72 characters
3. Use imperative mood (e.g., "add" not "added")
4. Be specific but concise

Return ONLY the commit message, nothing else."""

            response = self.model.invoke(prompt)
            if hasattr(response, "content"):
                message = response.content.strip()
                # Validate format
                if message and len(message.split("\n")[0]) <= 100:
                    return message

        except Exception:
            pass  # Fall back to template message

        return fallback

    def stage_files(self, files: list[str] | None = None) -> bool:
        """Stage files for commit.

        Args:
            files: List of file paths to stage. If None, stages all changes.

        Returns:
            True if staging was successful.
        """
        try:
            if files:
                cmd = ["git", "add"] + files
            else:
                cmd = ["git", "add", "-A"]

            result = subprocess.run(
                cmd,
                cwd=self.repo_path,
                capture_output=True,
                text=True,
            )
            return result.returncode == 0

        except Exception:
            return False

    def commit(
        self,
        message: Optional[str] = None,
        files: Optional[list[str]] = None,
        stage: bool = True,
        use_ai: bool = False,
    ) -> CommitResult:
        """Create a git commit.

        Args:
            message: Commit message. If None, auto-generates one.
            files: Specific files to commit. If None, commits all staged changes.
            stage: If True and files provided, stage them first.
            use_ai: If True, use AI for message generation.

        Returns:
            CommitResult with operation outcome.
        """
        try:
            # Stage files if requested
            if stage and files:
                if not self.stage_files(files):
                    return CommitResult(
                        success=False,
                        error="Failed to stage files",
                    )

            # Check for staged changes
            status = self.get_status()
            if not status["staged"]:
                return CommitResult(
                    success=False,
                    error="No staged changes to commit",
                )

            # Generate message if not provided
            if message is None:
                analysis = self.analyze_changes(staged_only=True)
                message = self.generate_commit_message(analysis, use_ai=use_ai)

            # Execute commit
            result = subprocess.run(
                ["git", "commit", "-m", message],
                cwd=self.repo_path,
                capture_output=True,
                text=True,
            )

            if result.returncode != 0:
                return CommitResult(
                    success=False,
                    message=message,
                    error=result.stderr.strip() or "Commit failed",
                )

            # Get commit hash
            hash_result = subprocess.run(
                ["git", "rev-parse", "HEAD"],
                cwd=self.repo_path,
                capture_output=True,
                text=True,
            )
            commit_hash = hash_result.stdout.strip()

            return CommitResult(
                success=True,
                commit_hash=commit_hash,
                message=message,
                files_committed=status["staged"],
                timestamp=datetime.now(),
            )

        except Exception as e:
            return CommitResult(
                success=False,
                error=str(e),
            )

    def get_recent_commits(self, count: int = 10) -> list[dict]:
        """Get recent commit history.

        Args:
            count: Number of commits to retrieve.

        Returns:
            List of commit dictionaries with hash, message, author, date.
        """
        try:
            result = subprocess.run(
                [
                    "git", "log",
                    f"-{count}",
                    "--pretty=format:%H|%s|%an|%ad",
                    "--date=iso",
                ],
                cwd=self.repo_path,
                capture_output=True,
                text=True,
            )

            commits = []
            for line in result.stdout.strip().split("\n"):
                if line:
                    parts = line.split("|", 3)
                    if len(parts) >= 4:
                        commits.append({
                            "hash": parts[0],
                            "message": parts[1],
                            "author": parts[2],
                            "date": parts[3],
                        })

            return commits

        except Exception:
            return []

    def get_diff(
        self,
        staged: bool = False,
        files: Optional[list[str]] = None,
    ) -> str:
        """Get diff output.

        Args:
            staged: If True, show staged changes only.
            files: Specific files to show diff for.

        Returns:
            Git diff output as string.
        """
        cmd = ["git", "diff"]
        if staged:
            cmd.append("--cached")
        if files:
            cmd.extend(["--"] + files)

        result = subprocess.run(
            cmd,
            cwd=self.repo_path,
            capture_output=True,
            text=True,
        )
        return result.stdout

    def undo_last_commit(self) -> CommitResult:
        """Undo the last commit by reverting it.

        Creates a new revert commit that undoes the changes from HEAD.

        Returns:
            CommitResult with operation outcome.
        """
        try:
            # Get the current HEAD info for reporting
            log_result = subprocess.run(
                ["git", "log", "-1", "--pretty=format:%H|%s"],
                cwd=self.repo_path,
                capture_output=True,
                text=True,
            )
            if log_result.returncode != 0:
                return CommitResult(success=False, error="No commits to undo")

            parts = log_result.stdout.strip().split("|", 1)
            old_hash = parts[0] if parts else ""
            old_msg = parts[1] if len(parts) > 1 else ""

            # Revert HEAD
            result = subprocess.run(
                ["git", "revert", "HEAD", "--no-edit"],
                cwd=self.repo_path,
                capture_output=True,
                text=True,
            )

            if result.returncode != 0:
                return CommitResult(
                    success=False,
                    error=result.stderr.strip() or "Revert failed",
                )

            # Get the new commit hash
            hash_result = subprocess.run(
                ["git", "rev-parse", "HEAD"],
                cwd=self.repo_path,
                capture_output=True,
                text=True,
            )

            return CommitResult(
                success=True,
                commit_hash=hash_result.stdout.strip(),
                message=f"Reverted: {old_msg} ({old_hash[:7]})",
                timestamp=datetime.now(),
            )

        except Exception as e:
            return CommitResult(success=False, error=str(e))

    def amend_commit(self, message: Optional[str] = None) -> CommitResult:
        """Amend the last commit.

        Args:
            message: New commit message. If None, keeps the existing message.

        Returns:
            CommitResult with operation outcome.
        """
        try:
            cmd = ["git", "commit", "--amend"]
            if message:
                cmd.extend(["-m", message])
            else:
                cmd.append("--no-edit")

            result = subprocess.run(
                cmd,
                cwd=self.repo_path,
                capture_output=True,
                text=True,
            )

            if result.returncode != 0:
                return CommitResult(
                    success=False,
                    error=result.stderr.strip() or "Amend failed",
                )

            # Get new commit hash
            hash_result = subprocess.run(
                ["git", "rev-parse", "HEAD"],
                cwd=self.repo_path,
                capture_output=True,
                text=True,
            )

            return CommitResult(
                success=True,
                commit_hash=hash_result.stdout.strip(),
                message=message or "(amended)",
                timestamp=datetime.now(),
            )

        except Exception as e:
            return CommitResult(
                success=False,
                error=str(e),
            )


# Convenience functions for tool usage
def git_status() -> dict:
    """Get git repository status."""
    return get_status()


def git_diff(staged: bool = False, files: Optional[list[str]] = None) -> str:
    """Get git diff output."""
    manager = AutoCommitManager()
    return manager.get_diff(staged=staged, files=files)


def git_commit(
    message: Optional[str] = None,
    files: Optional[list[str]] = None,
    use_ai: bool = False,
) -> dict:
    """Create a git commit.

    Args:
        message: Commit message. Auto-generates if not provided.
        files: Files to commit. All staged if not provided.
        use_ai: Use AI for message generation.

    Returns:
        Dictionary with commit result.
    """
    manager = AutoCommitManager()
    result = manager.commit(message=message, files=files, use_ai=use_ai)

    return {
        "success": result.success,
        "commit_hash": result.commit_hash,
        "message": result.message,
        "error": result.error,
        "files": result.files_committed,
    }


def generate_commit_message(use_ai: bool = False) -> str:
    """Generate a commit message for staged changes.

    Args:
        use_ai: Use AI for enhanced message generation.

    Returns:
        Generated commit message.
    """
    manager = AutoCommitManager()
    return manager.generate_commit_message(use_ai=use_ai)
