"""Install gjalla pre-commit and post-commit hooks."""

import sys
import subprocess
from dataclasses import dataclass
from enum import Enum
from pathlib import Path


def make_executable(path: Path) -> None:
    """Mark a file as executable. No-op on Windows (permissions are ACL-based)."""
    if sys.platform != "win32":
        path.chmod(0o755)


class InstallStatus(Enum):
    INSTALLED = "installed"
    UPDATED = "updated"
    ALREADY_EXISTS = "already_exists"
    ADDED_TO_EXISTING = "added_to_existing"
    FAILED = "failed"
    SKIPPED = "skipped"


@dataclass
class HookInstallResult:
    status: InstallStatus
    message: str
    hook_path: Path | None = None


@dataclass
class HooksInstallResult:
    pre_commit: HookInstallResult
    post_commit: HookInstallResult


GJALLA_MARKER = "# gjalla-precommit-hook"
GJALLA_POST_COMMIT_MARKER = "# gjalla-post-commit-hook"


def _resolve_hooks_dir(repo_path: Path) -> Path:
    """Resolve the git hooks directory, respecting core.hooksPath.

    Checks git config for core.hooksPath first. If set, uses that
    (relative paths are resolved from repo root). Falls back to .git/hooks.
    """
    try:
        result = subprocess.run(
            ["git", "config", "core.hooksPath"],
            capture_output=True,
            text=True,
            cwd=repo_path,
        )
        if result.returncode == 0 and result.stdout.strip():
            hooks_path = Path(result.stdout.strip())
            if not hooks_path.is_absolute():
                hooks_path = repo_path / hooks_path
            return hooks_path
    except (OSError, subprocess.SubprocessError):
        pass

    return repo_path / ".git" / "hooks"


def is_gjalla_installed(repo_path: Path) -> bool:
    """Check if gjalla hook is already installed."""
    hooks_dir = _resolve_hooks_dir(repo_path)
    hook_path = hooks_dir / "pre-commit"
    if not hook_path.exists():
        return False
    try:
        content = hook_path.read_text(encoding="utf-8")
        return GJALLA_MARKER in content or "gjalla-precommit" in content
    except OSError:
        return False


def install_hook(repo_path: Path, command: str = "") -> HookInstallResult:
    """Install gjalla pre-commit hook.

    If a hook already exists, prepends gjalla to it without overwriting.
    Respects core.hooksPath (e.g. .husky).
    """
    hooks_dir = _resolve_hooks_dir(repo_path)
    hook_path = hooks_dir / "pre-commit"

    if not hooks_dir.exists():
        # For custom hooksPath, create it; for default .git/hooks, fail if no .git
        if not (repo_path / ".git").exists():
            return HookInstallResult(
                status=InstallStatus.FAILED,
                message="Not a git repository (no .git directory)",
            )
        hooks_dir.mkdir(parents=True, exist_ok=True)

    if not command:
        return HookInstallResult(
            status=InstallStatus.SKIPPED,
            message="No hook command specified",
        )

    # Ensure the command propagates its exit code so a failing check
    # actually blocks the commit (the hook shell may not have set -e).
    guarded_command = f"{command} || exit $?" if "exit" not in command else command

    if hook_path.exists():
        try:
            existing = hook_path.read_text(encoding="utf-8")

            if GJALLA_MARKER in existing:
                # Replace existing gjalla block with updated command
                lines = existing.split("\n")
                new_lines = []
                skip_next = False
                for line in lines:
                    if line.strip() == GJALLA_MARKER:
                        new_lines.append(GJALLA_MARKER)
                        new_lines.append(guarded_command)
                        skip_next = True
                        continue
                    if skip_next:
                        skip_next = False
                        continue
                    new_lines.append(line)

                hook_path.write_text("\n".join(new_lines), encoding="utf-8")
                make_executable(hook_path)

                return HookInstallResult(
                    status=InstallStatus.UPDATED,
                    message=f"Updated gjalla pre-commit hook ({hooks_dir})",
                    hook_path=hook_path,
                )

            lines = existing.split("\n")
            insert_idx = 0
            for i, line in enumerate(lines):
                if line.startswith("#!"):
                    insert_idx = i + 1
                elif line.startswith("#") or line.strip() == "":
                    insert_idx = i + 1
                else:
                    break

            lines.insert(insert_idx, "")
            lines.insert(insert_idx + 1, GJALLA_MARKER)
            lines.insert(insert_idx + 2, guarded_command)
            lines.insert(insert_idx + 3, "")

            hook_path.write_text("\n".join(lines), encoding="utf-8")
            make_executable(hook_path)

            return HookInstallResult(
                status=InstallStatus.ADDED_TO_EXISTING,
                message=f"Added gjalla to existing pre-commit hook ({hooks_dir})",
                hook_path=hook_path,
            )
        except OSError as e:
            return HookInstallResult(
                status=InstallStatus.FAILED,
                message=f"Failed to modify hook: {e}",
            )
    else:
        hook_content = f"#!/bin/sh\n{GJALLA_MARKER}\n{guarded_command}\n"
        try:
            hook_path.write_text(hook_content, encoding="utf-8")
            make_executable(hook_path)
            return HookInstallResult(
                status=InstallStatus.INSTALLED,
                message=f"Installed git pre-commit hook ({hooks_dir})",
                hook_path=hook_path,
            )
        except OSError as e:
            return HookInstallResult(
                status=InstallStatus.FAILED,
                message=f"Failed to create hook: {e}",
            )


def install_post_commit_hook(repo_path: Path, command: str = "") -> HookInstallResult:
    """Install gjalla post-commit hook.

    If a hook already exists, appends gjalla to it without overwriting.
    Respects core.hooksPath (e.g. .husky).
    """
    hooks_dir = _resolve_hooks_dir(repo_path)
    hook_path = hooks_dir / "post-commit"

    if not hooks_dir.exists():
        if not (repo_path / ".git").exists():
            return HookInstallResult(
                status=InstallStatus.FAILED,
                message="Not a git repository (no .git directory)",
            )
        hooks_dir.mkdir(parents=True, exist_ok=True)

    if not command:
        return HookInstallResult(
            status=InstallStatus.SKIPPED,
            message="No hook command specified",
        )

    if hook_path.exists():
        try:
            existing = hook_path.read_text(encoding="utf-8")

            if GJALLA_POST_COMMIT_MARKER in existing:
                # Replace existing gjalla block with updated command
                lines = existing.split("\n")
                new_lines = []
                skip_next = False
                for line in lines:
                    if line.strip() == GJALLA_POST_COMMIT_MARKER:
                        new_lines.append(GJALLA_POST_COMMIT_MARKER)
                        new_lines.append(command)
                        skip_next = True
                        continue
                    if skip_next:
                        skip_next = False
                        continue
                    new_lines.append(line)

                hook_path.write_text("\n".join(new_lines), encoding="utf-8")
                make_executable(hook_path)

                return HookInstallResult(
                    status=InstallStatus.UPDATED,
                    message=f"Updated gjalla post-commit hook ({hooks_dir})",
                    hook_path=hook_path,
                )

            new_content = existing.rstrip() + f"\n\n{GJALLA_POST_COMMIT_MARKER}\n{command}\n"
            hook_path.write_text(new_content, encoding="utf-8")
            make_executable(hook_path)

            return HookInstallResult(
                status=InstallStatus.ADDED_TO_EXISTING,
                message=f"Added gjalla to existing post-commit hook ({hooks_dir})",
                hook_path=hook_path,
            )
        except OSError as e:
            return HookInstallResult(
                status=InstallStatus.FAILED,
                message=f"Failed to modify hook: {e}",
            )
    else:
        hook_content = f"#!/bin/sh\n{GJALLA_POST_COMMIT_MARKER}\n{command}\n"
        try:
            hook_path.write_text(hook_content, encoding="utf-8")
            make_executable(hook_path)
            return HookInstallResult(
                status=InstallStatus.INSTALLED,
                message=f"Installed git post-commit hook ({hooks_dir})",
                hook_path=hook_path,
            )
        except OSError as e:
            return HookInstallResult(
                status=InstallStatus.FAILED,
                message=f"Failed to create hook: {e}",
            )


def install_hooks(
    repo_path: Path,
    pre_commit_command: str = "",
    post_commit_command: str = "",
) -> HooksInstallResult:
    """Install both pre-commit and post-commit hooks."""
    pre_commit_result = install_hook(repo_path, command=pre_commit_command)
    post_commit_result = install_post_commit_hook(repo_path, command=post_commit_command)

    return HooksInstallResult(
        pre_commit=pre_commit_result,
        post_commit=post_commit_result,
    )
