"""Claude Code installation and configuration helpers."""

from __future__ import annotations

import json
import shutil
import subprocess
from pathlib import Path
from typing import Any

from installer.platform_utils import command_exists
from installer.steps.deps_utils import _run_bash_with_retry


def _remove_native_claude_binaries() -> None:
    """Remove native-installed Claude Code to avoid conflicts with npm install."""
    native_bin = Path.home() / ".local" / "bin" / "claude"
    native_data = Path.home() / ".local" / "share" / "claude"

    if native_bin.exists() or native_bin.is_symlink():
        try:
            native_bin.unlink()
        except Exception:
            pass

    if native_data.exists():
        shutil.rmtree(native_data, ignore_errors=True)


def _patch_claude_config(config_updates: dict[str, Any]) -> bool:
    """Patch ~/.claude.json with the given config updates.

    Creates the file if it doesn't exist. Merges updates with existing config.
    """
    config_path = Path.home() / ".claude.json"

    try:
        if config_path.exists():
            config = json.loads(config_path.read_text())
        else:
            config = {}

        config.update(config_updates)
        config_path.write_text(json.dumps(config, indent=2) + "\n")
        return True
    except Exception:
        return False


def _patch_claude_settings(settings_updates: dict[str, Any]) -> bool:
    """Patch ~/.claude/settings.json with the given settings updates.

    Creates the file if it doesn't exist. Merges updates with existing settings.
    """
    settings_dir = Path.home() / ".claude"
    settings_dir.mkdir(parents=True, exist_ok=True)
    settings_path = settings_dir / "settings.json"

    try:
        if settings_path.exists():
            settings = json.loads(settings_path.read_text())
        else:
            settings = {}

        settings.update(settings_updates)
        settings_path.write_text(json.dumps(settings, indent=2) + "\n")
        return True
    except Exception:
        return False


def _configure_claude_defaults() -> bool:
    """Configure Claude Code with recommended defaults after installation."""
    config_ok = _patch_claude_config(
        {
            "installMethod": "npm",
            "theme": "dark-ansi",
            "verbose": True,
            "autoCompactEnabled": True,
            "autoConnectIde": True,
            "showExpandedTodos": True,
            "autoUpdates": False,
            "lspRecommendationDisabled": True,
            "showTurnDuration": False,
            "terminalProgressBarEnabled": True,
            "preferredNotifChannel": "iterm2_with_bell",
        }
    )
    settings_ok = _patch_claude_settings(
        {
            "attribution": {"commit": "", "pr": ""},
            "respectGitignore": False,
            "cleanupPeriodDays": 7,
        }
    )
    return config_ok and settings_ok


def _get_forced_claude_version(project_dir: Path) -> str | None:
    """Check ~/.claude/settings.json for FORCE_CLAUDE_VERSION in env section."""
    _ = project_dir
    settings_path = Path.home() / ".claude" / "settings.json"
    if settings_path.exists():
        try:
            settings = json.loads(settings_path.read_text())
            return settings.get("env", {}).get("FORCE_CLAUDE_VERSION")
        except (json.JSONDecodeError, OSError):
            pass
    return None


def _clean_npm_stale_dirs() -> None:
    """Remove stale .claude-code-* temp dirs that cause npm ENOTEMPTY errors."""
    if not command_exists("npm"):
        return

    try:
        result = subprocess.run(
            ["npm", "root", "-g"],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            return

        node_modules_dir = Path(result.stdout.strip())
        anthropic_dir = node_modules_dir / "@anthropic-ai"
        if not anthropic_dir.exists():
            return

        for stale_dir in anthropic_dir.glob(".claude-code-*"):
            if stale_dir.is_dir():
                shutil.rmtree(stale_dir, ignore_errors=True)
    except Exception:
        pass


def _get_installed_claude_version() -> str | None:
    """Probe the actual installed Claude Code version via claude --version."""
    try:
        result = subprocess.run(
            ["claude", "--version"],
            capture_output=True,
            text=True,
        )
        if result.returncode == 0 and result.stdout.strip():
            return result.stdout.strip()
    except Exception:
        pass
    return None


def install_claude_code(project_dir: Path, ui: Any = None) -> tuple[bool, str]:
    """Install/upgrade Claude Code CLI via npm and configure defaults."""
    _remove_native_claude_binaries()
    _clean_npm_stale_dirs()

    forced_version = _get_forced_claude_version(project_dir)
    version = forced_version if forced_version else "latest"

    if version != "latest":
        npm_cmd = f"npm install -g @anthropic-ai/claude-code@{version}"
        if ui:
            ui.status(f"Installing Claude Code v{version}...")
    else:
        npm_cmd = "npm install -g @anthropic-ai/claude-code"
        if ui:
            ui.status("Installing Claude Code...")

    if not _run_bash_with_retry(npm_cmd):
        if command_exists("claude"):
            _configure_claude_defaults()
            actual_version = _get_installed_claude_version()
            return True, actual_version or version
        return False, version

    _configure_claude_defaults()
    return True, version


def _clean_mcp_servers_from_claude_config(ui: Any) -> None:
    """Remove mcpServers section from ~/.claude.json (now in plugin/.mcp.json)."""
    claude_config_path = Path.home() / ".claude.json"

    try:
        if not claude_config_path.exists():
            return

        config = json.loads(claude_config_path.read_text())

        if "mcpServers" not in config:
            return

        del config["mcpServers"]
        claude_config_path.write_text(json.dumps(config, indent=2) + "\n")

        if ui:
            ui.success("Cleaned mcpServers from ~/.claude.json (now in plugin/.mcp.json)")
    except Exception as e:
        if ui:
            ui.warning(f"Could not clean mcpServers from config: {e}")
