"""obol repair - fix corrupted or missing hooks.

Usage:
    obol repair
    obol repair --dry-run

Checks all installed handler files and hook configurations,
re-installs anything that is corrupted or missing.
"""
import hashlib
import json
import os
import shutil
import sys


def setup_parser(subparsers):
    p = subparsers.add_parser("repair", help="Repair corrupted or missing hooks")
    p.add_argument("--dry-run", action="store_true",
                   help="Show what would be fixed without changing anything")
    p.set_defaults(func=cmd_repair)


def _sha256(path):
    """Compute SHA-256 hex digest of a file."""
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            h.update(chunk)
    return h.hexdigest()


def cmd_repair(args):
    dry_run = args.dry_run

    print()
    print("  OBOL REPAIR")
    if dry_run:
        print("  Dry-run mode: no changes will be made")
    else:
        print("  Checking and repairing hooks")
    print()

    fixed = []
    ok = []
    errors = []

    # --- Step 1: Load config ---
    print("  [1/4] Configuration")
    config_path = os.path.expanduser("~/.obol/config.json")
    config = {}
    if os.path.exists(config_path):
        try:
            with open(config_path) as f:
                config = json.load(f)
            print("        ~/.obol/config.json loaded")
        except Exception as e:
            errors.append(f"Config file corrupted: {e}")
            print(f"        Config file corrupted: {e}")
            print("        Run 'obol setup' to recreate configuration.")
            print()
            _print_summary(ok, fixed, errors)
            return 1
    else:
        errors.append("~/.obol/config.json not found")
        print("        ~/.obol/config.json NOT FOUND")
        print("        Run 'obol setup' to create configuration.")
        print()
        _print_summary(ok, fixed, errors)
        return 1
    print()

    # --- Step 2: Check handler files ---
    print("  [2/4] Handler file integrity")

    handler_checks = [
        {
            "name": "PostToolUse handler",
            "installed_path": os.path.expanduser("~/.obol/handler.py"),
            "module": "obol.hooks.claude_code",
            "config_hash_key": "hook_handler_hash",
        },
        {
            "name": "PreToolUse gate",
            "installed_path": os.path.expanduser("~/.obol/gate.py"),
            "module": "obol.hooks.gate",
            "config_hash_key": "hook_gate_hash",
        },
    ]

    for check in handler_checks:
        name = check["name"]
        installed = check["installed_path"]
        display_path = installed.replace(os.path.expanduser("~"), "~")

        # Locate the source module
        try:
            mod = __import__(check["module"], fromlist=["__file__"])
            source_path = os.path.abspath(mod.__file__)
        except Exception as e:
            errors.append(f"Cannot locate source for {name}: {e}")
            print(f"        {name}: source module not found ({e})")
            continue

        if not os.path.exists(installed):
            # Handler missing -- re-install
            print(f"        {display_path}: MISSING")
            if dry_run:
                print(f"          Would re-install from SDK")
                fixed.append(f"{name} would be re-installed")
            else:
                try:
                    _install_handler(source_path, installed)
                    new_hash = _sha256(installed)
                    config[check["config_hash_key"]] = new_hash
                    print(f"          Re-installed (read-only)")
                    fixed.append(f"{name} re-installed")
                except Exception as e:
                    errors.append(f"Could not re-install {name}: {e}")
                    print(f"          ERROR: {e}")
        else:
            # Handler exists -- verify integrity
            current_hash = _sha256(installed)
            expected_hash = config.get(check["config_hash_key"])
            source_hash = _sha256(source_path)

            # Check permissions
            mode = os.stat(installed).st_mode & 0o777

            if expected_hash and current_hash == expected_hash and mode == 0o444:
                print(f"        {display_path}: OK (hash matches, read-only)")
                ok.append(f"{name} integrity verified")
            elif current_hash == source_hash and mode == 0o444:
                # Hash matches source even if config hash is missing/stale
                print(f"        {display_path}: OK (matches SDK source)")
                if not expected_hash:
                    if not dry_run:
                        config[check["config_hash_key"]] = current_hash
                    fixed.append(f"{name} hash stored in config")
                ok.append(f"{name} integrity verified")
            else:
                # Corrupted or tampered
                reasons = []
                if expected_hash and current_hash != expected_hash:
                    reasons.append("hash mismatch")
                if mode != 0o444:
                    reasons.append(f"mode {oct(mode)} != 0444")
                if current_hash != source_hash:
                    reasons.append("differs from SDK source")

                print(f"        {display_path}: CORRUPTED ({', '.join(reasons)})")

                if dry_run:
                    print(f"          Would re-install from SDK")
                    fixed.append(f"{name} would be re-installed")
                else:
                    try:
                        _install_handler(source_path, installed)
                        new_hash = _sha256(installed)
                        config[check["config_hash_key"]] = new_hash
                        print(f"          Re-installed (read-only)")
                        fixed.append(f"{name} re-installed")
                    except Exception as e:
                        errors.append(f"Could not re-install {name}: {e}")
                        print(f"          ERROR: {e}")
    print()

    # --- Step 3: Check hook entries in agent configs ---
    print("  [3/4] Hook configuration")

    from obol.cli._base import quote_path

    handler_path = os.path.expanduser("~/.obol/handler.py")
    gate_path = os.path.expanduser("~/.obol/gate.py")

    # Claude Code hooks
    settings_path = os.path.expanduser("~/.claude/settings.json")
    if os.path.exists(settings_path):
        try:
            with open(settings_path) as f:
                settings = json.load(f)
        except Exception as e:
            errors.append(f"Claude Code settings corrupted: {e}")
            print(f"        Claude Code (~/.claude/settings.json): CORRUPTED ({e})")
            settings = None

        if settings is not None:
            hooks = settings.get("hooks", {})
            hooks_str = json.dumps(hooks)
            has_handler = "handler.py" in hooks_str or "claude_code" in hooks_str
            has_gate = "gate.py" in hooks_str

            if has_handler and has_gate:
                print("        Claude Code: hooks present (handler + gate)")
                ok.append("Claude Code hook entries present")
            else:
                missing = []
                if not has_handler:
                    missing.append("PostToolUse handler")
                if not has_gate:
                    missing.append("PreToolUse gate")

                print(f"        Claude Code: MISSING ({', '.join(missing)})")

                if dry_run:
                    print(f"          Would re-add hook entries")
                    fixed.append(f"Claude Code hook entries would be re-added")
                else:
                    try:
                        _repair_claude_code_hooks(settings, settings_path,
                                                  handler_path, gate_path)
                        print(f"          Hook entries re-added")
                        fixed.append("Claude Code hook entries re-added")
                    except Exception as e:
                        errors.append(f"Could not repair Claude Code hooks: {e}")
                        print(f"          ERROR: {e}")
    else:
        print("        Claude Code: not installed (no ~/.claude/settings.json)")

    # Cursor hooks
    cursor_hooks_path = os.path.expanduser("~/.cursor/hooks.json")
    if os.path.exists(cursor_hooks_path):
        try:
            with open(cursor_hooks_path) as f:
                cursor_data = json.load(f)
            cursor_str = json.dumps(cursor_data)
            if "obol" in cursor_str:
                print("        Cursor: hooks present")
                ok.append("Cursor hook entries present")
            else:
                print("        Cursor: config exists, Obol NOT installed")
                print("          Run 'obol setup' to install Cursor hooks")
        except Exception as e:
            print(f"        Cursor: config corrupted ({e})")
    else:
        print("        Cursor: not installed")

    # Windsurf hooks
    windsurf_hooks_path = os.path.expanduser("~/.codeium/windsurf/hooks.json")
    if os.path.exists(windsurf_hooks_path):
        try:
            with open(windsurf_hooks_path) as f:
                windsurf_data = json.load(f)
            windsurf_str = json.dumps(windsurf_data)
            if "obol" in windsurf_str:
                print("        Windsurf: hooks present")
                ok.append("Windsurf hook entries present")
            else:
                print("        Windsurf: config exists, Obol NOT installed")
                print("          Run 'obol setup' to install Windsurf hooks")
        except Exception as e:
            print(f"        Windsurf: config corrupted ({e})")
    else:
        print("        Windsurf: not installed")
    print()

    # --- Step 4: Save updated config ---
    print("  [4/4] Saving configuration")
    if not dry_run and fixed:
        try:
            with open(config_path, "w") as f:
                json.dump(config, f, indent=2)
            from obol.cli._base import secure_file
            secure_file(config_path)
            print("        Config updated with new integrity hashes")
        except Exception as e:
            errors.append(f"Could not save config: {e}")
            print(f"        ERROR saving config: {e}")
    elif dry_run:
        print("        Dry-run: config not modified")
    else:
        print("        No changes needed")
    print()

    # --- Summary ---
    _print_summary(ok, fixed, errors)
    return 1 if errors else 0


def _install_handler(source_path, dest_path):
    """Copy handler from SDK source to installed location as read-only."""
    dest_dir = os.path.dirname(dest_path)
    os.makedirs(dest_dir, exist_ok=True)

    # Remove old copy if it exists (may be read-only)
    if os.path.exists(dest_path):
        os.chmod(dest_path, 0o644)
        os.unlink(dest_path)

    shutil.copy2(source_path, dest_path)
    os.chmod(dest_path, 0o444)


def _repair_claude_code_hooks(settings, settings_path, handler_path, gate_path):
    """Re-add Obol hook entries to Claude Code settings.json."""
    from obol.cli._base import quote_path

    hook_cmd = f"{quote_path(sys.executable)} {quote_path(handler_path)}"
    gate_cmd = f"{quote_path(sys.executable)} {quote_path(gate_path)}"

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

    hooks = settings["hooks"]

    obol_hook = {
        "type": "command",
        "command": hook_cmd,
        "timeout": 10,
    }

    obol_gate = {
        "type": "command",
        "command": gate_cmd,
        "timeout": 3,
    }

    hook_events = {
        "PreToolUse": {"matcher": ".*", "hooks": [obol_gate]},
        "PostToolUse": {"matcher": ".*", "hooks": [{**obol_hook, "async": True}]},
        "PostToolUseFailure": {"matcher": ".*", "hooks": [{**obol_hook, "async": True}]},
        "SessionStart": {"hooks": [{**obol_hook, "async": True}]},
        "SessionEnd": {"hooks": [{**obol_hook, "async": True}]},
    }

    def _has_obol_hook(hook_list):
        existing_json = json.dumps(hook_list)
        return "obol" in existing_json and (
            "claude_code" in existing_json
            or "handler.py" in existing_json
            or "gate.py" in existing_json
        )

    for event, handler in hook_events.items():
        if event not in hooks:
            hooks[event] = [handler]
        else:
            if not _has_obol_hook(hooks[event]):
                hooks[event].append(handler)
            else:
                # Replace existing obol hook entry with updated command
                for i, entry in enumerate(hooks[event]):
                    entry_json = json.dumps(entry)
                    if "obol" in entry_json and (
                        "claude_code" in entry_json
                        or "handler.py" in entry_json
                        or "gate.py" in entry_json
                    ):
                        hooks[event][i] = handler
                        break

    with open(settings_path, "w") as f:
        json.dump(settings, f, indent=2)


def _print_summary(ok, fixed, errors):
    """Print repair summary."""
    print("  " + "=" * 48)
    print()

    if ok:
        print("  VERIFIED:")
        for item in ok:
            print(f"    + {item}")
        print()

    if fixed:
        print("  REPAIRED:")
        for item in fixed:
            print(f"    * {item}")
        print()

    if errors:
        print("  ERRORS (could not fix):")
        for item in errors:
            print(f"    - {item}")
        print()

    if not fixed and not errors:
        print("  Everything looks good. No repairs needed.")
    elif fixed and not errors:
        print("  Repairs complete. Hooks are restored.")
    elif errors:
        print("  Some issues could not be auto-repaired.")
        print("  Run 'obol setup' for a full re-installation.")
    print()
