"""File generation engine for TokenGuard.

Generates files split across two locations:
- User-level (~/.claude/): rules, hooks, settings.json, memory
- Project-level (<project>/.claude/): CLAUDE.md, .tokenguard.json
"""

import hashlib
import json
from datetime import datetime, timezone
from importlib import resources
from pathlib import Path
from typing import Optional

from jinja2 import Environment, PackageLoader

from . import __version__
from .detector import detect_grip_global, has_grip_hook


def _hash_content(content: str) -> str:
    """SHA-256 hash of content for change tracking."""
    return hashlib.sha256(content.encode("utf-8")).hexdigest()[:16]


def _get_template_env() -> Environment:
    """Create Jinja2 template environment."""
    return Environment(
        loader=PackageLoader("tokenguard", "templates"),
        keep_trailing_newline=True,
        trim_blocks=True,
        lstrip_blocks=True,
    )


def _read_template_file(subdir: str, filename: str) -> str:
    """Read a static template file from the templates directory."""
    ref = resources.files("tokenguard") / "templates" / subdir / filename
    return ref.read_text(encoding="utf-8")


def generate_claude_md(
    project_type: str,
    grip_global: bool,
    project_name: Optional[str] = None,
) -> str:
    """Generate CLAUDE.md content.

    Args:
        project_type: Detected project type.
        grip_global: Whether full GRIP is installed globally.
        project_name: Optional project name for the header.

    Returns:
        Generated CLAUDE.md content.
    """
    env = _get_template_env()
    base = env.get_template("claude_md/base.md.j2")

    # Load type-specific additions
    type_template_name = f"claude_md/{project_type}.md.j2"
    try:
        type_additions = env.get_template(type_template_name).render()
    except Exception:
        type_additions = env.get_template("claude_md/generic.md.j2").render()

    return base.render(
        project_type=project_type,
        grip_global=grip_global,
        project_name=project_name or "Project",
        type_additions=type_additions,
        version=__version__,
    )


def merge_hook_into_settings(
    settings_path: Path,
    hook_command: str,
    status_message: str = "TokenGuard gate check",
) -> str:
    """Merge TokenGuard hook into existing settings.json.

    Reads the existing file (if any), adds the hook without removing
    existing hooks, and returns the merged JSON string.

    Args:
        settings_path: Path to the settings.json file.
        hook_command: The hook command to register.
        status_message: Status message shown during hook execution.

    Returns:
        JSON string for the merged settings.json.
    """
    if settings_path.exists():
        try:
            settings = json.loads(settings_path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            settings = {}
    else:
        settings = {
            "$schema": "https://docs.anthropic.com/claude-code/settings.schema.json",
        }

    if "hooks" not in settings:
        settings["hooks"] = {}
    if "PreToolUse" not in settings["hooks"]:
        settings["hooks"]["PreToolUse"] = []

    # Check if our hook is already registered
    for group in settings["hooks"]["PreToolUse"]:
        for hook in group.get("hooks", []):
            if "mirror-hook.py" in hook.get("command", ""):
                # Already registered — return as-is
                return json.dumps(settings, indent=2) + "\n"

    # Add our hook
    settings["hooks"]["PreToolUse"].append(
        {
            "matcher": ".*",
            "hooks": [
                {
                    "type": "command",
                    "command": hook_command,
                    "statusMessage": status_message,
                }
            ],
        }
    )

    return json.dumps(settings, indent=2) + "\n"


def generate_mirror_json(
    project_type: str,
    grip_global: bool,
    user_file_hashes: dict,
    project_file_hashes: dict,
    installed_components: list,
) -> str:
    """Generate .tokenguard.json tracking metadata.

    Args:
        project_type: Detected project type.
        grip_global: Whether GRIP is installed globally.
        user_file_hashes: Map of user-level relative path -> content hash.
        project_file_hashes: Map of project-level relative path -> content hash.
        installed_components: List of installed component names.

    Returns:
        JSON string for .tokenguard.json.
    """
    data = {
        "version": __version__,
        "project_type": project_type,
        "grip_global": grip_global,
        "installed_at": datetime.now(timezone.utc).isoformat(),
        "user_file_hashes": user_file_hashes,
        "project_file_hashes": project_file_hashes,
        "installed_components": installed_components,
    }
    return json.dumps(data, indent=2) + "\n"


def scaffold_project(
    project_dir: Path,
    project_type: str,
    standalone: bool = False,
    no_hooks: bool = False,
    dry_run: bool = False,
    user_claude_dir: Optional[Path] = None,
) -> dict:
    """Generate all TokenGuard files for a project.

    Installs user-level files (rules, hooks, settings, memory) to ~/.claude/
    and project-level files (CLAUDE.md, tracking) to <project>/.claude/.

    Args:
        project_dir: Root directory of the target project.
        project_type: Detected or overridden project type.
        standalone: Force standalone mode even if GRIP detected globally.
        no_hooks: Skip hook installation.
        dry_run: Only return what would be created, don't write.
        user_claude_dir: Override ~/.claude/ location (for testing).

    Returns:
        Dict with keys:
            - user_files: dict of path relative to ~/.claude/ -> content
            - project_files: dict of path relative to project_dir -> content
            - mode: "standalone" or "grip-user"
            - project_type: detected type
            - hooks_skipped: True if GRIP hooks detected
            - skipped_existing: list of user-level files that already existed
    """
    if user_claude_dir is None:
        user_claude_dir = Path.home() / ".claude"

    grip_global = detect_grip_global() and not standalone
    mode = "grip-user" if grip_global else "standalone"

    user_files = {}
    project_files = {}
    user_file_hashes = {}
    project_file_hashes = {}
    skipped_existing = []

    installed_components = ["claude-md"]

    # CLAUDE.md -> project level (always generated)
    project_name = project_dir.name
    claude_md = generate_claude_md(project_type, grip_global, project_name)
    project_files[".claude/CLAUDE.md"] = claude_md
    project_file_hashes[".claude/CLAUDE.md"] = _hash_content(claude_md)

    # Memory scaffold -> user level (always, unless already exists)
    memory_content = _read_template_file("memory", "MEMORY.md.j2")
    memory_path = user_claude_dir / "memory" / "MEMORY.md"
    if not memory_path.exists() or not dry_run:
        if memory_path.exists():
            skipped_existing.append("memory/MEMORY.md")
        else:
            user_files["memory/MEMORY.md"] = memory_content
            user_file_hashes["memory/MEMORY.md"] = _hash_content(memory_content)
            installed_components.append("memory")

    if not grip_global:
        # Standalone mode: install rules to user level
        rule_files = [
            "efficiency-rules.md",
            "bash-safety.md",
            "commit-standards.md",
            "session-protocol.md",
            "python-async-safety.md",
            "path-portability.md",
            "operational-principles.md",
        ]
        rules_installed = False
        for rule_file in rule_files:
            content = _read_template_file("rules", rule_file)
            rel_path = f"rules/{rule_file}"
            rule_full_path = user_claude_dir / rel_path
            if rule_full_path.exists():
                skipped_existing.append(rel_path)
            else:
                user_files[rel_path] = content
                user_file_hashes[rel_path] = _hash_content(content)
                rules_installed = True
        if rules_installed:
            installed_components.append("rules")

    # Check for existing GRIP hook — skip mirror hook if GRIP is already active
    grip_hook_active = has_grip_hook(project_dir, user_claude_dir=user_claude_dir) and not standalone
    hooks_skipped = False

    if not no_hooks and not grip_hook_active:
        # Install portable hook -> user level
        hook_content = _read_template_file("hooks", "mirror-hook.py")
        hook_rel = "hooks/mirror-hook.py"
        hook_full_path = user_claude_dir / hook_rel
        if hook_full_path.exists():
            skipped_existing.append(hook_rel)
        else:
            user_files[hook_rel] = hook_content
            user_file_hashes[hook_rel] = _hash_content(hook_content)
        installed_components.append("hooks")

        # Merge hook into ~/.claude/settings.json
        hook_cmd = "python3 ~/.claude/hooks/mirror-hook.py"
        settings_content = merge_hook_into_settings(
            user_claude_dir / "settings.json", hook_cmd
        )
        user_files["settings.json"] = settings_content
        # Don't hash settings — it's a merge target with user's own hooks
        installed_components.append("settings")
    elif grip_hook_active:
        hooks_skipped = True

    # Generate .tokenguard.json -> project level
    mirror_json = generate_mirror_json(
        project_type,
        grip_global,
        user_file_hashes,
        project_file_hashes,
        installed_components,
    )
    project_files[".claude/.tokenguard.json"] = mirror_json

    if not dry_run:
        # Write user-level files
        for rel_path, content in user_files.items():
            full_path = user_claude_dir / rel_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text(content, encoding="utf-8")

        # Write project-level files
        for rel_path, content in project_files.items():
            full_path = project_dir / rel_path
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text(content, encoding="utf-8")

    return {
        "user_files": user_files,
        "project_files": project_files,
        "mode": mode,
        "project_type": project_type,
        "hooks_skipped": hooks_skipped,
        "skipped_existing": skipped_existing,
    }
