"""Update/diff mechanism for TokenGuard.

Compares installed files against current templates, preserving user
customizations via hash comparison. Handles both user-level (~/.claude/)
and project-level (<project>/.claude/) files.
"""

import json
from pathlib import Path
from typing import Optional

from . import __version__
from .scaffold import _hash_content


def _get_tracking(project_dir: Path) -> Optional[dict]:
    """Read .tokenguard.json tracking data.

    Handles both v0.2 format (file_hashes) and v0.3+ format
    (user_file_hashes + project_file_hashes).
    """
    mirror_file = project_dir / ".claude" / ".tokenguard.json"
    if not mirror_file.exists():
        return None
    try:
        return json.loads(mirror_file.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def _get_all_hashes(tracking: dict) -> tuple:
    """Extract user and project file hashes from tracking data.

    Returns (user_file_hashes, project_file_hashes).
    Handles v0.2 format by treating all hashes as project-level.
    """
    if "user_file_hashes" in tracking:
        # v0.3+ format
        return (
            tracking.get("user_file_hashes", {}),
            tracking.get("project_file_hashes", {}),
        )
    # v0.2 format — all hashes are project-level
    return ({}, tracking.get("file_hashes", {}))


def _get_current_template_outputs(
    project_dir: Path,
    user_claude_dir: Optional[Path] = None,
) -> tuple:
    """Get what templates would generate now via dry-run scaffold.

    Returns (user_files, project_files) dicts of rel_path -> content.
    """
    from .scaffold import scaffold_project

    tracking = _get_tracking(project_dir)
    if not tracking:
        return ({}, {})

    project_type = tracking.get("project_type", "generic")
    grip_global = tracking.get("grip_global", False)

    result = scaffold_project(
        project_dir=project_dir,
        project_type=project_type,
        standalone=not grip_global,
        dry_run=True,
        user_claude_dir=user_claude_dir,
    )

    return (result["user_files"], result["project_files"])


def check_updates(
    project_dir: Path,
    user_claude_dir: Optional[Path] = None,
) -> dict:
    """Check which files have available updates.

    Compares installed file hashes against current template hashes.
    Files modified by the user (hash mismatch with installed hash) are
    flagged as user-modified and skipped during update.

    Args:
        project_dir: Root directory of the target project.
        user_claude_dir: Override ~/.claude/ location (for testing).

    Returns:
        Dict with keys:
            - updatable: list of (location, rel_path) tuples
            - user_modified: list of display paths
            - current: list of display paths
            - missing_tracking: True if .tokenguard.json not found
    """
    if user_claude_dir is None:
        user_claude_dir = Path.home() / ".claude"

    tracking = _get_tracking(project_dir)
    if not tracking:
        return {
            "updatable": [],
            "user_modified": [],
            "current": [],
            "missing_tracking": True,
        }

    user_hashes, project_hashes = _get_all_hashes(tracking)

    # Get what templates would generate now
    try:
        tmpl_user, tmpl_project = _get_current_template_outputs(
            project_dir, user_claude_dir
        )
    except (json.JSONDecodeError, OSError):
        tmpl_user, tmpl_project = ({}, {})

    updatable = []
    user_modified = []
    current = []

    # Check user-level files
    for rel_path, installed_hash in user_hashes.items():
        full_path = user_claude_dir / rel_path
        display = f"~/.claude/{rel_path}"

        if not full_path.exists():
            updatable.append(("user", rel_path))
            continue

        current_hash = _hash_content(full_path.read_text(encoding="utf-8"))
        if current_hash != installed_hash:
            user_modified.append(display)
        else:
            template_content = tmpl_user.get(rel_path)
            if template_content:
                template_hash = _hash_content(template_content)
                if template_hash != installed_hash:
                    updatable.append(("user", rel_path))
                else:
                    current.append(display)
            else:
                current.append(display)

    # Check project-level files
    for rel_path, installed_hash in project_hashes.items():
        full_path = project_dir / rel_path
        display = rel_path

        if not full_path.exists():
            updatable.append(("project", rel_path))
            continue

        current_hash = _hash_content(full_path.read_text(encoding="utf-8"))
        if current_hash != installed_hash:
            user_modified.append(display)
        else:
            template_content = tmpl_project.get(rel_path)
            if template_content:
                template_hash = _hash_content(template_content)
                if template_hash != installed_hash:
                    updatable.append(("project", rel_path))
                else:
                    current.append(display)
            else:
                current.append(display)

    return {
        "updatable": updatable,
        "user_modified": user_modified,
        "current": current,
        "missing_tracking": False,
    }


def apply_updates(
    project_dir: Path,
    updatable: list,
    user_claude_dir: Optional[Path] = None,
) -> list:
    """Apply template updates to specified files.

    Re-generates files from current templates and updates tracking hashes.

    Args:
        project_dir: Root directory of the target project.
        updatable: List of (location, rel_path) tuples from check_updates.
        user_claude_dir: Override ~/.claude/ location (for testing).

    Returns:
        List of display paths that were updated.
    """
    if user_claude_dir is None:
        user_claude_dir = Path.home() / ".claude"

    tracking = _get_tracking(project_dir)
    if not tracking:
        return []

    # Get fresh template content
    tmpl_user, tmpl_project = _get_current_template_outputs(
        project_dir, user_claude_dir
    )

    applied = []
    user_hashes = tracking.get("user_file_hashes", {})
    project_hashes = tracking.get("project_file_hashes", tracking.get("file_hashes", {}))

    for location, rel_path in updatable:
        if location == "user":
            content = tmpl_user.get(rel_path)
            if content:
                full_path = user_claude_dir / rel_path
                full_path.parent.mkdir(parents=True, exist_ok=True)
                full_path.write_text(content, encoding="utf-8")
                user_hashes[rel_path] = _hash_content(content)
                applied.append(f"~/.claude/{rel_path}")
        elif location == "project":
            content = tmpl_project.get(rel_path)
            if content:
                full_path = project_dir / rel_path
                full_path.parent.mkdir(parents=True, exist_ok=True)
                full_path.write_text(content, encoding="utf-8")
                project_hashes[rel_path] = _hash_content(content)
                applied.append(rel_path)

    # Update tracking file
    mirror_file = project_dir / ".claude" / ".tokenguard.json"
    tracking["user_file_hashes"] = user_hashes
    tracking["project_file_hashes"] = project_hashes
    # Remove old v0.2 key if migrating
    tracking.pop("file_hashes", None)
    tracking["version"] = __version__
    mirror_file.write_text(json.dumps(tracking, indent=2) + "\n", encoding="utf-8")

    return applied


def get_status(project_dir: Path) -> Optional[dict]:
    """Get TokenGuard installation status.

    Args:
        project_dir: Root directory of the target project.

    Returns:
        Status dict or None if not initialized.
    """
    tracking = _get_tracking(project_dir)
    if not tracking:
        return None

    return {
        "version": tracking.get("version", "unknown"),
        "project_type": tracking.get("project_type", "unknown"),
        "grip_global": tracking.get("grip_global", False),
        "installed_at": tracking.get("installed_at", "unknown"),
        "components": tracking.get("installed_components", []),
        "latest_version": __version__,
        "update_available": tracking.get("version") != __version__,
    }


def eject(project_dir: Path, user_claude_dir: Optional[Path] = None) -> list:
    """Remove TokenGuard tracking, leave files as user-owned.

    Args:
        project_dir: Root directory of the target project.
        user_claude_dir: Override ~/.claude/ location (for testing).

    Returns:
        List of actions taken.
    """
    if user_claude_dir is None:
        user_claude_dir = Path.home() / ".claude"

    actions = []
    mirror_file = project_dir / ".claude" / ".tokenguard.json"

    if mirror_file.exists():
        mirror_file.unlink()
        actions.append("Removed .tokenguard.json tracking file")

    # Remove hook registration from ~/.claude/settings.json
    settings_file = user_claude_dir / "settings.json"
    if settings_file.exists():
        try:
            settings = json.loads(settings_file.read_text(encoding="utf-8"))
            hooks = settings.get("hooks", {}).get("PreToolUse", [])
            new_hooks = []
            for hook_group in hooks:
                filtered = [
                    h
                    for h in hook_group.get("hooks", [])
                    if "mirror-hook.py" not in h.get("command", "")
                ]
                if filtered:
                    hook_group["hooks"] = filtered
                    new_hooks.append(hook_group)
            if new_hooks != hooks:
                settings["hooks"]["PreToolUse"] = new_hooks
                settings_file.write_text(
                    json.dumps(settings, indent=2) + "\n", encoding="utf-8"
                )
                actions.append("Removed mirror-hook from ~/.claude/settings.json")
        except (json.JSONDecodeError, OSError):
            pass

    if not actions:
        actions.append("No TokenGuard tracking found")

    return actions
