"""Git hook templates and installation logic for buildlog workflow enforcement."""

from __future__ import annotations

import os
import stat
from pathlib import Path

import yaml

# Pre-commit hook: prevent commits to main/master
PRE_COMMIT_HOOK = """\
#!/bin/sh
# buildlog: prevent direct commits to main/master
branch=$(git branch --show-current)
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
    echo "\\033[31m[buildlog] Direct commits to $branch are not allowed.\\033[0m"
    echo "Create a feature branch: git checkout -b feat/your-feature"
    exit 1
fi
"""

# Post-commit hook: nudge toward buildlog_commit (or block if enforced)
POST_COMMIT_HOOK = """\
#!/bin/sh
# buildlog: remind to use buildlog_commit instead of raw git commit
if [ -z "$BUILDLOG_COMMIT" ]; then
    echo ""
    echo "\\033[33m[buildlog] Tip: use 'buildlog commit -m \"...\"' instead of 'git commit'\\033[0m"
    echo "  buildlog_commit wraps git and auto-logs to your journal entry."
fi
"""

# Pre-commit hook: enforce buildlog_commit (always on)
# buildlog_commit() sets BUILDLOG_COMMIT=1 to bypass this hook.
# Set BUILDLOG_ENFORCE=0 to disable (opt-out, not opt-in).
ENFORCE_COMMIT_HOOK = """\
#!/bin/sh
# buildlog: block bare git commit — use buildlog_commit() instead
# buildlog_commit() sets BUILDLOG_COMMIT=1 to bypass this hook.
# Set BUILDLOG_ENFORCE=0 to disable.
if [ "${BUILDLOG_ENFORCE:-1}" != "0" ] && [ -z "$BUILDLOG_COMMIT" ]; then
    echo ""
    echo "\\033[31m[buildlog] Direct git commit blocked.\\033[0m"
    echo "  Use: buildlog commit -m \\"your message\\""
    echo "  Or:  buildlog_commit(message=\\"...\\") via MCP"
    echo "  To bypass once: BUILDLOG_COMMIT=1 git commit -m \\"...\\""
    echo "  To disable enforcement: export BUILDLOG_ENFORCE=0"
    exit 1
fi
"""

# Marker to identify buildlog-managed hook sections
_HOOK_MARKER = "# buildlog:"

# pre-commit-config.yaml entry for branch protection (string form for tests)
PRE_COMMIT_CONFIG_ENTRY = (
    "  - repo: local\n"
    "    hooks:\n"
    "      - id: prevent-commit-to-main\n"
    '        name: "buildlog: prevent commits to main/master"\n'
    "        entry: bash -c '\n"
    "          branch=$(git branch --show-current);\n"
    '          if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then\n'
    '            echo "[buildlog] Direct commits to $branch not allowed.";\n'
    "            exit 1;\n"
    "          fi'\n"
    "        language: system\n"
    "        always_run: true\n"
    "        pass_filenames: false\n"
)

# Dict forms used for proper YAML manipulation in install_hooks()
_PRE_COMMIT_HOOK_DICT: dict = {
    "repo": "local",
    "hooks": [
        {
            "id": "prevent-commit-to-main",
            "name": "buildlog: prevent commits to main/master",
            "entry": (
                "bash -c '"
                "branch=$(git branch --show-current); "
                'if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then '
                'echo "[buildlog] Direct commits to $branch not allowed."; '
                "exit 1; "
                "fi'"
            ),
            "language": "system",
            "always_run": True,
            "pass_filenames": False,
        }
    ],
}

_ENFORCE_HOOK_DICT: dict = {
    "repo": "local",
    "hooks": [
        {
            "id": "enforce-buildlog-commit",
            "name": "buildlog: enforce buildlog_commit()",
            "entry": (
                "bash -c '"
                'if [ "${BUILDLOG_ENFORCE:-1}" != "0" ] && [ -z "$BUILDLOG_COMMIT" ]; then '
                'echo "[buildlog] Direct git commit blocked. '
                'Use buildlog_commit() or set BUILDLOG_ENFORCE=0."; '
                "exit 1; "
                "fi'"
            ),
            "language": "system",
            "always_run": True,
            "pass_filenames": False,
        }
    ],
}


def _make_executable(path: Path) -> None:
    """Add executable permission to a file."""
    st = os.stat(path)
    os.chmod(path, st.st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)


def install_hooks(
    project_dir: Path,
    no_hooks: bool = False,
) -> dict:
    """Install buildlog git hooks into a project.

    Strategy:
    1. If .pre-commit-config.yaml exists, add branch protection + enforcement
       as local hooks in the yaml (pre-commit framework uses exec, so appending
       to .git/hooks/pre-commit would be dead code).
    2. Otherwise, install standalone .git/hooks/pre-commit with both hooks.
    3. Always install .git/hooks/post-commit nudge.
    """
    if no_hooks:
        return {"installed": [], "message": "Hook installation skipped (--no-hooks)"}

    installed: list[str] = []
    messages: list[str] = []

    git_dir = project_dir / ".git"
    if not git_dir.exists():
        return {
            "installed": [],
            "message": "Not a git repository — skipping hook installation",
        }

    hooks_dir = git_dir / "hooks"
    hooks_dir.mkdir(exist_ok=True)

    # --- Pre-commit: branch protection + enforcement ---
    pre_commit_config = project_dir / ".pre-commit-config.yaml"
    if pre_commit_config.exists():
        # When pre-commit framework is in use, add hooks via yaml config.
        # Appending to .git/hooks/pre-commit won't work because the
        # pre-commit framework uses `exec`, making appended code unreachable.
        content = pre_commit_config.read_text()
        config = yaml.safe_load(content) or {}
        repos = config.get("repos") or []
        changed = False

        if "prevent-commit-to-main" not in content:
            repos.append(_PRE_COMMIT_HOOK_DICT)
            installed.append("pre-commit-config (branch protection)")
            changed = True

        if "enforce-buildlog-commit" not in content:
            repos.append(_ENFORCE_HOOK_DICT)
            installed.append("pre-commit-config (enforce buildlog_commit)")
            changed = True

        if changed:
            config["repos"] = repos
            pre_commit_config.write_text(
                yaml.dump(config, default_flow_style=False, sort_keys=False)
            )
            messages.append(
                "Updated .pre-commit-config.yaml. "
                "Run 'pre-commit install' to activate."
            )
        else:
            messages.append("All hooks already in .pre-commit-config.yaml")
    else:
        # No pre-commit framework — install standalone hooks
        _install_standalone_hook(
            hooks_dir / "pre-commit", PRE_COMMIT_HOOK, installed, messages
        )
        # Enforcement appends to the same pre-commit file (no exec issue
        # since we wrote the file ourselves)
        _install_standalone_hook(
            hooks_dir / "pre-commit", ENFORCE_COMMIT_HOOK, installed, messages
        )

    # --- Post-commit: buildlog_commit nudge ---
    _install_standalone_hook(
        hooks_dir / "post-commit", POST_COMMIT_HOOK, installed, messages
    )

    return {
        "installed": installed,
        "message": "; ".join(messages) if messages else "Hooks up to date",
    }


def _install_standalone_hook(
    hook_path: Path,
    hook_content: str,
    installed: list[str],
    messages: list[str],
) -> None:
    """Install a standalone git hook, chaining with any existing hook."""
    hook_name = hook_path.name

    if hook_path.exists():
        existing = hook_path.read_text()
        # Use the specific marker line from this hook (e.g. "# buildlog: prevent direct")
        # to allow multiple buildlog hooks in the same file.
        specific_marker = next(
            (
                line.strip()
                for line in hook_content.splitlines()
                if line.strip().startswith(_HOOK_MARKER)
            ),
            _HOOK_MARKER,
        )
        if specific_marker in existing:
            messages.append(f"{hook_name}: buildlog hook already installed")
            return
        # Chain: append our hook after existing content (strip shebang line)
        lines = hook_content.split("\n", 1)
        body = lines[1] if len(lines) > 1 else hook_content
        with hook_path.open("a") as f:
            f.write("\n" + body)
        installed.append(f"{hook_name} (appended)")
        messages.append(f"{hook_name}: appended buildlog hook to existing")
    else:
        hook_path.write_text(hook_content)
        installed.append(hook_name)
        messages.append(f"{hook_name}: installed")

    _make_executable(hook_path)
