"""Golem MCP installer — copies plugin + skills into a Godot project."""

import json
import os
import shutil
import sys
from pathlib import Path
from typing import Callable

# ══════════════════════════════════════════════════════════════════════════════
# Agent configurations
# ══════════════════════════════════════════════════════════════════════════════

AgentConfig = dict[str, any]

AGENT_CONFIGS: dict[str, AgentConfig] = {
    "claude": {
        "name": "Claude Code",
        "path": lambda project: project / ".mcp.json",
        "key": "mcpServers",
        "global": False,
    },
    "cursor": {
        "name": "Cursor",
        "path": lambda project: project / ".cursor" / "mcp.json",
        "key": "mcpServers",
        "global": False,
    },
    "windsurf": {
        "name": "Windsurf",
        "path": lambda _: Path.home() / ".codeium" / "windsurf" / "mcp_config.json",
        "key": "mcpServers",
        "global": True,
    },
    "continue": {
        "name": "Continue.dev",
        "path": lambda project: project / ".continue" / "config.json",
        "key": "mcpServers",
        "global": False,
    },
    "cline": {
        "name": "Cline",
        "manual": True,
    },
}

AGENT_INSTRUCTIONS: dict[str, str] = {
    "claude": """
→ Next steps:
  1. Enable plugin in Godot > Project Settings > Plugins
  2. Run: claude
  3. Type: /godot or /init
""",
    "cursor": """
→ Next steps:
  1. Enable plugin in Godot > Project Settings > Plugins
  2. Open project in Cursor
  3. The Godot MCP will be available automatically
""",
    "windsurf": """
→ Next steps:
  1. Enable plugin in Godot > Project Settings > Plugins
  2. Open project in Windsurf
  3. The Godot MCP is configured globally and ready to use
""",
    "continue": """
→ Next steps:
  1. Enable plugin in Godot > Project Settings > Plugins
  2. Open project in your editor with Continue.dev
  3. The Godot MCP will be available in Continue
""",
    "cline": """
→ Next steps:
  1. Enable plugin in Godot > Project Settings > Plugins
  2. Open VS Code with Cline extension
  3. Go to Cline settings > MCP Servers
  4. Add a new server with:
     - Name: godot
     - Command: uvx golem-mcp
     - Type: stdio
""",
}


def _get_version() -> str:
    try:
        from importlib.metadata import version
        return version("golem-mcp")
    except Exception:
        toml = Path(__file__).parent / "pyproject.toml"
        if toml.exists():
            for line in toml.read_text().splitlines():
                if line.strip().startswith("version"):
                    return line.split('"')[1]
        return "0.0.0"


def _get_data_dir() -> Path:
    """Locate bundled assets: installed package or dev checkout."""
    # Installed via pip/uvx — assets in golem_mcp_data/
    pkg_data = Path(__file__).parent / "golem_mcp_data"
    if (pkg_data / "godot-plugin").is_dir():
        return pkg_data

    # Dev checkout — assets at repo root
    repo = Path(__file__).parent
    if (repo / "godot-plugin").is_dir():
        return repo

    print("✗ Cannot locate Golem MCP assets (godot-plugin/ not found)")
    sys.exit(1)


def _validate_project(path: Path) -> None:
    if not path.is_dir():
        print(f"✗ Directory not found: {path}")
        sys.exit(1)
    if not (path / "project.godot").exists():
        print(f"✗ Not a Godot project (no project.godot): {path}")
        sys.exit(1)


def _copy_dir(src: Path, dst: Path) -> int:
    """Copy src directory to dst, return file count."""
    if dst.exists():
        shutil.rmtree(dst)
    shutil.copytree(src, dst)
    return sum(1 for f in dst.rglob("*") if f.is_file())


def _copy_plugin(data_dir: Path, project: Path) -> int:
    src = data_dir / "godot-plugin"
    dst = project / "addons" / "golem-mcp"
    return _copy_dir(src, dst)


def _copy_skills(data_dir: Path, project: Path) -> int:
    # In installed package: golem_mcp_data/commands/
    # In dev checkout: .claude/commands/
    src = data_dir / "commands"
    if not src.exists():
        src = data_dir / ".claude" / "commands"
    if not src.exists():
        print(f"  ⚠ Skills not found in {data_dir}")
        return 0

    dst = project / ".claude" / "commands"
    dst.mkdir(parents=True, exist_ok=True)
    count = 0
    for md in src.glob("*.md"):
        shutil.copy2(md, dst / md.name)
        count += 1
    return count


def _copy_hooks(data_dir: Path, project: Path) -> int:
    src = data_dir / "hooks"
    dst = project / ".claude" / "hooks"
    dst.mkdir(parents=True, exist_ok=True)
    count = 0
    for py in src.glob("*.py"):
        shutil.copy2(py, dst / py.name)
        count += 1
    return count


def _get_python_command() -> str:
    """Get the correct Python command for the current platform."""
    if sys.platform == "win32":
        return "python"
    return "python3"


def _generate_settings_json(project: Path) -> None:
    settings_file = project / ".claude" / "settings.json"
    python_cmd = _get_python_command()

    settings = {
        "hooks": {
            "PreToolUse": [
                {
                    "matcher": "mcp__godot__.*",
                    "hooks": [
                        {
                            "type": "command",
                            "command": f"{python_cmd} .claude/hooks/check_connection.py",
                            "timeout": 3,
                            "statusMessage": "Checking Godot connection..."
                        }
                    ]
                }
            ],
            "PostToolUse": [
                {
                    "matcher": "mcp__godot__godot_write_script",
                    "hooks": [
                        {
                            "type": "command",
                            "command": f"{python_cmd} .claude/hooks/gdscript_lint.py",
                            "timeout": 5,
                            "statusMessage": "Linting GDScript..."
                        }
                    ]
                },
                {
                    "matcher": "Edit|Write",
                    "hooks": [
                        {
                            "type": "command",
                            "command": f"{python_cmd} .claude/hooks/check_project_sync.py",
                            "timeout": 3,
                            "statusMessage": "Syncing project state..."
                        }
                    ]
                }
            ]
        }
    }

    # Merge with existing settings if present
    if settings_file.exists():
        try:
            existing = json.loads(settings_file.read_text())
            existing.setdefault("hooks", {})
            existing["hooks"].update(settings["hooks"])
            settings = existing
        except (json.JSONDecodeError, OSError):
            pass

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


def _write_version(project: Path, version: str) -> None:
    vfile = project / "addons" / "golem-mcp" / "version.txt"
    vfile.write_text(version + "\n")


def _build_server_config(local_path: str | None) -> dict:
    """Build the MCP server configuration."""
    if local_path:
        return {
            "type": "stdio",
            "command": "uv",
            "args": ["run", "--directory", local_path, "golem-mcp"],
        }
    return {
        "type": "stdio",
        "command": "uvx",
        "args": ["golem-mcp"],
    }


def _generate_agent_config(project: Path, agent: str, local_path: str | None) -> str | None:
    """Generate MCP config for the specified agent.

    Returns:
        Path to the generated config file, or None if manual setup required.
    """
    config = AGENT_CONFIGS.get(agent)
    if not config:
        print(f"✗ Unknown agent: {agent}")
        print(f"  Available: {', '.join(AGENT_CONFIGS.keys())}")
        sys.exit(1)

    # Manual setup required (e.g., Cline)
    if config.get("manual"):
        return None

    path_fn: Callable[[Path], Path] = config["path"]
    config_path = path_fn(project)
    key = config["key"]

    # Ensure parent directory exists
    config_path.parent.mkdir(parents=True, exist_ok=True)

    # Load existing config or create new
    if config_path.exists():
        try:
            data = json.loads(config_path.read_text())
        except (json.JSONDecodeError, OSError):
            data = {}
    else:
        data = {}

    # Add or update the godot server config
    data.setdefault(key, {})
    data[key]["godot"] = _build_server_config(local_path)

    config_path.write_text(json.dumps(data, indent=2) + "\n")
    return str(config_path)


def _generate_mcp_json(project: Path, local_path: str | None) -> None:
    """Generate .mcp.json for Claude Code (legacy function for backwards compatibility)."""
    _generate_agent_config(project, "claude", local_path)


def _check_existing(project: Path, force: bool) -> bool:
    """Return True if we should proceed."""
    addon_dir = project / "addons" / "golem-mcp"
    if addon_dir.exists() and not force:
        answer = input("Golem MCP already installed. Overwrite? [y/N] ").strip().lower()
        return answer in ("y", "yes")
    return True


# ══════════════════════════════════════════════════════════════════════════════
# Interactive mode helpers
# ══════════════════════════════════════════════════════════════════════════════

def _detect_agents() -> list[str]:
    """Detect installed AI coding assistants on the system."""
    agents = []

    # Claude Code
    if shutil.which("claude"):
        agents.append("claude")

    # Cursor (check for app on macOS/Windows)
    if sys.platform == "darwin":
        if Path("/Applications/Cursor.app").exists():
            agents.append("cursor")
    elif sys.platform == "win32":
        cursor_paths = [
            Path(os.environ.get("LOCALAPPDATA", "")) / "Programs" / "cursor" / "Cursor.exe",
            Path(os.environ.get("PROGRAMFILES", "")) / "Cursor" / "Cursor.exe",
        ]
        if any(p.exists() for p in cursor_paths):
            agents.append("cursor")
    else:  # Linux
        # Check for cursor binary, AppImage, or Flatpak
        local_bin = Path.home() / ".local" / "bin"
        cursor_found = (
            shutil.which("cursor") or
            (local_bin.exists() and any(local_bin.glob("cursor*"))) or
            Path("/var/lib/flatpak/app/com.cursor.Cursor").exists()
        )
        if cursor_found:
            agents.append("cursor")

    # Windsurf (Codeium)
    windsurf_config = Path.home() / ".codeium" / "windsurf"
    if windsurf_config.exists():
        agents.append("windsurf")

    # Continue.dev (VS Code extension — check config dir)
    continue_config = Path.home() / ".continue"
    if continue_config.exists():
        agents.append("continue")

    # Cline (VS Code extension — harder to detect, add if others are found)
    # Just include it as an option if we detect VS Code extensions
    vscode_extensions = Path.home() / ".vscode" / "extensions"
    if vscode_extensions.exists():
        for ext in vscode_extensions.iterdir():
            if "cline" in ext.name.lower() or "claude-dev" in ext.name.lower():
                agents.append("cline")
                break

    return agents


def _detect_godot_project() -> Path | None:
    """Find a Godot project in current directory or immediate subdirectories."""
    cwd = Path.cwd()

    # Check current directory
    if (cwd / "project.godot").exists():
        return cwd

    # Check immediate subdirectories
    for child in cwd.iterdir():
        if child.is_dir() and (child / "project.godot").exists():
            return child

    return None


def _get_godot_version(project: Path) -> str | None:
    """Extract Godot version from project.godot file."""
    project_file = project / "project.godot"
    if not project_file.exists():
        return None

    try:
        content = project_file.read_text()
        for line in content.splitlines():
            # Look for config/features line which contains version
            if "config/features" in line:
                # Format: config/features=PackedStringArray("4.3", "GL Compatibility")
                if "4.4" in line:
                    return "4.4"
                elif "4.3" in line:
                    return "4.3"
                elif "4.2" in line:
                    return "4.2"
                elif "4.1" in line:
                    return "4.1"
                elif "4.0" in line:
                    return "4.0"
        return "4.x"
    except OSError:
        return None


def _prompt_choice(question: str, options: list[tuple[str, str]], default: int = 0) -> int:
    """Display a menu and return the selected index.

    Args:
        question: Question to display
        options: List of (label, description) tuples
        default: Default selection index

    Returns:
        Selected index, or -1 if cancelled
    """
    print(f"\n? {question}")
    for i, (label, desc) in enumerate(options):
        marker = "●" if i == default else "○"
        if desc:
            print(f"  {marker} {label} — {desc}")
        else:
            print(f"  {marker} {label}")

    print()
    try:
        answer = input(f"  Enter choice [1-{len(options)}] (default: {default + 1}): ").strip()
        if not answer:
            return default
        idx = int(answer) - 1
        if 0 <= idx < len(options):
            return idx
        return default
    except (ValueError, EOFError, KeyboardInterrupt):
        return -1


def _prompt_confirm(question: str, default: bool = True) -> bool:
    """Prompt for yes/no confirmation."""
    suffix = "[Y/n]" if default else "[y/N]"
    try:
        answer = input(f"\n? {question} {suffix} ").strip().lower()
        if not answer:
            return default
        return answer in ("y", "yes")
    except (EOFError, KeyboardInterrupt):
        return False


def _interactive_install() -> tuple[Path | None, str]:
    """Run interactive installation wizard.

    Returns:
        Tuple of (project_path, agent_name) or (None, "") if cancelled
    """
    from golem_mcp import _print_banner

    _print_banner()
    print("  Interactive Setup")
    print("  " + "─" * 50)
    print()
    print("  Detecting environment...")

    # Detect agents
    detected_agents = _detect_agents()
    all_agents = list(AGENT_CONFIGS.keys())

    # Build agent options
    agent_options = []
    for agent_id in all_agents:
        config = AGENT_CONFIGS[agent_id]
        name = config.get("name", agent_id)
        detected = "(detected)" if agent_id in detected_agents else ""
        agent_options.append((name, detected))

    # Select agent
    default_agent_idx = 0
    if detected_agents:
        # Prefer first detected agent
        for i, agent_id in enumerate(all_agents):
            if agent_id in detected_agents:
                default_agent_idx = i
                break

    agent_idx = _prompt_choice("Which AI assistant are you using?", agent_options, default_agent_idx)
    if agent_idx < 0:
        print("\nAborted.")
        return None, ""

    selected_agent = all_agents[agent_idx]

    # Detect Godot project
    detected_project = _detect_godot_project()

    if detected_project:
        version = _get_godot_version(detected_project) or "4.x"
        project_options = [
            (f"./{detected_project.name}", f"Godot {version} — detected"),
            ("Enter path manually", ""),
        ]
        project_idx = _prompt_choice("Which Godot project?", project_options, 0)

        if project_idx < 0:
            print("\nAborted.")
            return None, ""
        elif project_idx == 0:
            project = detected_project
        else:
            try:
                path_str = input("\n  Enter project path: ").strip()
                project = Path(path_str).resolve()
            except (EOFError, KeyboardInterrupt):
                print("\nAborted.")
                return None, ""
    else:
        print("\n  No Godot project detected in current directory.")
        try:
            path_str = input("  Enter project path: ").strip()
            if not path_str:
                print("\nAborted.")
                return None, ""
            project = Path(path_str).resolve()
        except (EOFError, KeyboardInterrupt):
            print("\nAborted.")
            return None, ""

    # Confirm
    agent_name = AGENT_CONFIGS[selected_agent].get("name", selected_agent)
    if not _prompt_confirm(f"Install Golem MCP in {project} for {agent_name}?"):
        print("\nAborted.")
        return None, ""

    return project, selected_agent


def _parse_flag_value(argv: list[str], flag: str) -> str | None:
    """Extract a flag value from argv (e.g., --local /path)."""
    for i, a in enumerate(argv):
        if a == flag and i + 1 < len(argv):
            return argv[i + 1]
    return None


def run_install(argv: list[str]) -> None:
    # Parse flags
    force = "--force" in argv
    skip_skills = "--skip-skills" in argv
    local_path = _parse_flag_value(argv, "--local")
    agent = _parse_flag_value(argv, "--agent") or "claude"

    # Get positional args (exclude flags and their values)
    skip_next = False
    args = []
    for i, a in enumerate(argv):
        if skip_next:
            skip_next = False
            continue
        if a.startswith("--"):
            if a in ("--local", "--agent"):
                skip_next = True
            continue
        args.append(a)

    # Interactive mode if no project specified
    if not args:
        project, agent = _interactive_install()
        if project is None:
            return
    else:
        project = Path(args[0]).resolve()

    version = _get_version()

    _validate_project(project)

    if not _check_existing(project, force):
        print("Aborted.")
        return

    data_dir = _get_data_dir()

    # Copy plugin
    plugin_count = _copy_plugin(data_dir, project)
    _write_version(project, version)

    # Copy skills (only for Claude Code, which uses .claude/commands/)
    skills_count = 0
    if not skip_skills and agent == "claude":
        skills_count = _copy_skills(data_dir, project)

    # Copy hooks (only for Claude Code)
    hooks_count = 0
    if agent == "claude":
        hooks_count = _copy_hooks(data_dir, project)
        _generate_settings_json(project)

    # Generate agent-specific config
    config_path = _generate_agent_config(project, agent, local_path)

    agent_config = AGENT_CONFIGS.get(agent, {})
    agent_name = agent_config.get("name", agent)

    # Summary
    print()
    print(f"✓ Golem MCP v{version} installed in {project}")
    print(f"  Agent:   {agent_name}")
    print(f"  Plugin:  addons/golem-mcp/ ({plugin_count} files)")
    if skills_count:
        print(f"  Skills:  .claude/commands/ ({skills_count} files)")
    if hooks_count:
        print(f"  Hooks:   .claude/hooks/ ({hooks_count} files)")
    if config_path:
        print(f"  Config:  {config_path}")

    # Agent-specific instructions
    instructions = AGENT_INSTRUCTIONS.get(agent, "")
    if instructions:
        print(instructions)


# ══════════════════════════════════════════════════════════════════════════════
# Uninstall
# ══════════════════════════════════════════════════════════════════════════════

# Skills that are part of Golem MCP (to avoid removing user's own skills)
GOLEM_SKILLS = [
    "godot.md",
    "godot-plan.md",
    "godot-architecture.md",
    "godot-scene.md",
    "godot-composition.md",
    "godot-behavior.md",
    "godot-juice.md",
    "godot-vfx.md",
    "godot-theme.md",
    "godot-debug.md",
    "godot-design-audit.md",
    "godot-status.md",
    "init.md",
    "checkpoint.md",
    "references.md",
    "deliverable.md",
    "golem-dev.md",
    "INDEX.md",
]

# Hooks that are part of Golem MCP
GOLEM_HOOKS = [
    "check_connection.py",
    "gdscript_lint.py",
    "copy_skills.py",
    "check_project_sync.py",
]


def _remove_plugin(project: Path) -> bool:
    """Remove the Golem MCP plugin directory."""
    plugin_dir = project / "addons" / "golem-mcp"
    if plugin_dir.exists():
        shutil.rmtree(plugin_dir)
        return True
    return False


def _remove_skills(project: Path) -> int:
    """Remove Golem MCP skills from .claude/commands/."""
    commands_dir = project / ".claude" / "commands"
    if not commands_dir.exists():
        return 0

    count = 0
    for skill in GOLEM_SKILLS:
        skill_file = commands_dir / skill
        if skill_file.exists():
            skill_file.unlink()
            count += 1

    # Remove directory if empty
    if commands_dir.exists() and not any(commands_dir.iterdir()):
        commands_dir.rmdir()

    return count


def _remove_hooks(project: Path) -> int:
    """Remove Golem MCP hooks from .claude/hooks/."""
    hooks_dir = project / ".claude" / "hooks"
    if not hooks_dir.exists():
        return 0

    count = 0
    for hook in GOLEM_HOOKS:
        hook_file = hooks_dir / hook
        if hook_file.exists():
            hook_file.unlink()
            count += 1

    # Remove directory if empty
    if hooks_dir.exists() and not any(hooks_dir.iterdir()):
        hooks_dir.rmdir()

    return count


def _clean_mcp_configs(project: Path) -> list[str]:
    """Remove 'godot' entry from all known MCP config files."""
    cleaned = []

    for agent_id, config in AGENT_CONFIGS.items():
        if config.get("manual"):
            continue

        path_fn = config["path"]
        config_path = path_fn(project)

        if not config_path.exists():
            continue

        try:
            data = json.loads(config_path.read_text())
            key = config["key"]

            if key in data and "godot" in data[key]:
                del data[key]["godot"]

                # Remove key if empty
                if not data[key]:
                    del data[key]

                # Remove file if empty, else rewrite
                if not data or data == {}:
                    config_path.unlink()
                else:
                    config_path.write_text(json.dumps(data, indent=2) + "\n")

                cleaned.append(str(config_path))
        except (json.JSONDecodeError, OSError):
            pass

    return cleaned


def _clean_settings_json(project: Path) -> bool:
    """Remove Golem MCP hooks from .claude/settings.json."""
    settings_file = project / ".claude" / "settings.json"
    if not settings_file.exists():
        return False

    try:
        data = json.loads(settings_file.read_text())
        modified = False

        if "hooks" in data:
            # Remove PreToolUse hooks matching golem patterns
            if "PreToolUse" in data["hooks"]:
                original_len = len(data["hooks"]["PreToolUse"])
                data["hooks"]["PreToolUse"] = [
                    h for h in data["hooks"]["PreToolUse"]
                    if not (h.get("matcher", "").startswith("mcp__godot__"))
                ]
                if len(data["hooks"]["PreToolUse"]) != original_len:
                    modified = True
                if not data["hooks"]["PreToolUse"]:
                    del data["hooks"]["PreToolUse"]

            # Remove PostToolUse hooks that reference golem hooks
            if "PostToolUse" in data["hooks"]:
                original_len = len(data["hooks"]["PostToolUse"])
                data["hooks"]["PostToolUse"] = [
                    h for h in data["hooks"]["PostToolUse"]
                    if not any(
                        "golem" in hook.get("command", "").lower() or
                        ".claude/hooks/" in hook.get("command", "")
                        for hook in h.get("hooks", [])
                    )
                ]
                if len(data["hooks"]["PostToolUse"]) != original_len:
                    modified = True
                if not data["hooks"]["PostToolUse"]:
                    del data["hooks"]["PostToolUse"]

            # Remove hooks key if empty
            if not data["hooks"]:
                del data["hooks"]

        if modified:
            if data:
                settings_file.write_text(json.dumps(data, indent=2) + "\n")
            else:
                settings_file.unlink()
            return True

    except (json.JSONDecodeError, OSError):
        pass

    return False


def run_uninstall(argv: list[str]) -> None:
    """Uninstall Golem MCP from a Godot project."""
    force = "--force" in argv
    args = [a for a in argv if not a.startswith("--")]

    if not args:
        # Try to detect project in current directory
        project = _detect_godot_project()
        if not project:
            print("Usage: golem-mcp uninstall <project_path>")
            print()
            print("No Godot project found in current directory.")
            sys.exit(1)
    else:
        project = Path(args[0]).resolve()

    _validate_project(project)

    # Check if Golem MCP is installed
    plugin_dir = project / "addons" / "golem-mcp"
    if not plugin_dir.exists():
        print(f"✗ Golem MCP is not installed in {project}")
        return

    # Confirm
    if not force:
        answer = input(f"Uninstall Golem MCP from {project}? [y/N] ").strip().lower()
        if answer not in ("y", "yes"):
            print("Aborted.")
            return

    # Remove components
    removed_plugin = _remove_plugin(project)
    removed_skills = _remove_skills(project)
    removed_hooks = _remove_hooks(project)
    cleaned_configs = _clean_mcp_configs(project)
    cleaned_settings = _clean_settings_json(project)

    # Clean up empty .claude directory
    claude_dir = project / ".claude"
    if claude_dir.exists():
        # Check if only empty subdirs remain
        remaining = list(claude_dir.rglob("*"))
        remaining_files = [f for f in remaining if f.is_file()]
        if not remaining_files:
            shutil.rmtree(claude_dir)

    # Summary
    print()
    print(f"✓ Golem MCP uninstalled from {project}")
    if removed_plugin:
        print("  Removed: addons/golem-mcp/")
    if removed_skills:
        print(f"  Removed: {removed_skills} skill files")
    if removed_hooks:
        print(f"  Removed: {removed_hooks} hook files")
    if cleaned_configs:
        for cfg in cleaned_configs:
            print(f"  Cleaned: {cfg}")
    if cleaned_settings:
        print("  Cleaned: .claude/settings.json")
