"""Install cleave skill to Claude Code skills directory.

This module handles finding the package's skill directory and creating
a symlink in ~/.claude/skills/cleave.

TODO: Refactor to support multiple agentic interfaces beyond Claude Code.
      The current implementation is Claude Code specific (~/.claude/skills/).
      Future interfaces may use different discovery paths:
      - Cursor: ~/.cursor/skills/ or similar
      - Windsurf: ~/.windsurf/skills/ or similar
      - Generic MCP: ~/.config/mcp-skills/ or similar
      Consider an interface registry or plugin pattern.
"""

from __future__ import annotations

import importlib.metadata
import importlib.resources
import platform
import shutil
from dataclasses import dataclass
from pathlib import Path
from typing import Literal


@dataclass
class InstallResult:
    """Result of skill installation attempt."""

    success: bool
    action: Literal["created", "updated", "skipped", "error"]
    message: str
    source_path: Path | None
    target_path: Path | None
    used_copy: bool = False  # True if fallback to copy was used instead of symlink


def create_symlink_with_fallback(target: Path, source: Path) -> tuple[bool, str]:
    """Create symlink with fallback to directory copy on Windows.

    Args:
        target: The symlink path to create
        source: The directory to link to

    Returns:
        Tuple of (used_copy, error_message). used_copy=True if copy fallback was used.
        error_message is empty string on success.
    """
    try:
        target.symlink_to(source)
        return (False, "")
    except OSError as e:
        # On Windows, symlinks require Developer Mode or admin privileges
        # Fall back to copying the directory
        if platform.system() == "Windows":
            try:
                shutil.copytree(source, target, dirs_exist_ok=False)
                # Write version file to track installed version
                try:
                    version = importlib.metadata.version("styrene-cleave")
                    (target / ".version").write_text(version)
                except Exception:
                    pass  # Non-critical, continue without version tracking
                return (True, "")
            except Exception as copy_err:
                # Clean up partial copy on failure
                if target.exists():
                    try:
                        shutil.rmtree(target)
                    except Exception:
                        pass  # Best effort cleanup
                return (False, f"Symlink failed and copy fallback failed: {copy_err}")
        else:
            # On non-Windows, symlink failure is unexpected
            return (False, f"Failed to create symlink: {e}")


def check_windows_long_path(path: Path) -> tuple[bool, str]:
    """Check if path exceeds Windows MAX_PATH limit.

    Windows has a 260 character path limit unless:
    1. LongPathsEnabled registry key is set
    2. Application manifest declares long path awareness
    3. Path uses extended-length prefix (\\\\?\\)

    Args:
        path: Path to validate

    Returns:
        Tuple of (is_valid, error_message). is_valid=False if path too long.
    """
    if platform.system() != "Windows":
        return (True, "")  # No limit on Unix

    MAX_PATH = 260
    path_str = str(path.resolve())

    # Check if using extended-length prefix
    if path_str.startswith("\\\\?\\"):
        return (True, "")  # Extended-length paths support up to 32,767 chars

    if len(path_str) >= MAX_PATH:
        return (
            False,
            f"Path length ({len(path_str)}) exceeds Windows MAX_PATH limit ({MAX_PATH}). "
            "Enable long path support in Windows or use a shorter installation path.",
        )

    return (True, "")


def find_package_skill_dir() -> Path | None:
    """Find the skill directory within the installed cleave package.

    Works with pip installs, pipx installs, and editable installs.

    Returns:
        Path to the skill directory, or None if not found.
    """
    try:
        # Use importlib.resources to find the package location
        # This works regardless of install method (pip, pipx, editable)
        with importlib.resources.as_file(
            importlib.resources.files("cleave").joinpath("skill")
        ) as skill_path:
            if skill_path.exists() and skill_path.is_dir():
                return skill_path.resolve()
    except (TypeError, FileNotFoundError):
        pass

    # Fallback: try to find via __file__ (works for editable installs)
    try:
        import cleave

        package_dir = Path(cleave.__file__).parent
        skill_dir = package_dir / "skill"
        if skill_dir.exists() and skill_dir.is_dir():
            return skill_dir.resolve()
    except (AttributeError, TypeError):
        pass

    return None


def install_skill(home_dir: Path | None = None) -> InstallResult:
    """Install the cleave skill by creating a symlink.

    Creates a symlink at ~/.claude/skills/cleave pointing to the
    skill directory within the installed package.

    Args:
        home_dir: Override home directory (for testing). Defaults to Path.home().

    Returns:
        InstallResult with success status and details.
    """
    if home_dir is None:
        home_dir = Path.home()

    skills_dir = home_dir / ".claude" / "skills"
    target_path = skills_dir / "cleave"

    # Validate path length on Windows
    is_valid, error_msg = check_windows_long_path(target_path)
    if not is_valid:
        return InstallResult(
            success=False,
            action="error",
            message=error_msg,
            source_path=None,
            target_path=target_path,
        )

    # Find the source skill directory
    source_path = find_package_skill_dir()
    if source_path is None:
        return InstallResult(
            success=False,
            action="error",
            message="Could not find skill directory in cleave package",
            source_path=None,
            target_path=target_path,
        )

    # Validate resolved path stays within home directory (defense against symlink attacks)
    resolved_skills_dir = skills_dir.resolve()
    resolved_home = home_dir.resolve()
    # Use is_relative_to for cross-platform path comparison (Python 3.9+)
    if not resolved_skills_dir.is_relative_to(resolved_home):
        return InstallResult(
            success=False,
            action="error",
            message=f"Path escapes home directory: {resolved_skills_dir}",
            source_path=source_path,
            target_path=target_path,
        )

    # Create parent directories if needed
    skills_dir.mkdir(parents=True, exist_ok=True)

    # Check what exists at target path
    if target_path.exists() or target_path.is_symlink():
        if target_path.is_symlink():
            # It's a symlink - check where it points
            try:
                current_target = target_path.resolve()
                if current_target == source_path:
                    return InstallResult(
                        success=True,
                        action="skipped",
                        message=f"Skill already installed correctly at {target_path}",
                        source_path=source_path,
                        target_path=target_path,
                        used_copy=False,
                    )
                else:
                    # Points elsewhere - update it
                    # Use try/except to handle TOCTOU race condition
                    try:
                        target_path.unlink()
                        used_copy, error = create_symlink_with_fallback(target_path, source_path)
                        if error:
                            return InstallResult(
                                success=False,
                                action="error",
                                message=error,
                                source_path=source_path,
                                target_path=target_path,
                            )
                    except FileExistsError:
                        # Race condition: something appeared between unlink and symlink
                        return InstallResult(
                            success=False,
                            action="error",
                            message=f"Race condition: {target_path} was modified during update. Retry.",
                            source_path=source_path,
                            target_path=target_path,
                        )
                    link_type = "directory copy" if used_copy else "symlink"
                    return InstallResult(
                        success=True,
                        action="updated",
                        message=f"Updated {link_type} from {current_target} to {source_path}",
                        source_path=source_path,
                        target_path=target_path,
                        used_copy=used_copy,
                    )
            except OSError:
                # Broken symlink - remove and recreate
                try:
                    target_path.unlink()
                    used_copy, error = create_symlink_with_fallback(target_path, source_path)
                    if error:
                        return InstallResult(
                            success=False,
                            action="error",
                            message=error,
                            source_path=source_path,
                            target_path=target_path,
                        )
                except FileExistsError:
                    return InstallResult(
                        success=False,
                        action="error",
                        message=f"Race condition: {target_path} was modified during update. Retry.",
                        source_path=source_path,
                        target_path=target_path,
                    )
                link_type = "directory copy" if used_copy else "symlink"
                return InstallResult(
                    success=True,
                    action="updated",
                    message=f"Replaced broken symlink with {link_type} to {source_path}",
                    source_path=source_path,
                    target_path=target_path,
                    used_copy=used_copy,
                )
        elif target_path.is_dir():
            # Could be a previous copy-based install (Windows fallback)
            # Check if it has the expected SKILL.md file
            if (target_path / "SKILL.md").exists():
                # Check if version is current
                version_file = target_path / ".version"
                if version_file.exists():
                    try:
                        installed_version = version_file.read_text().strip()
                        current_version = importlib.metadata.version("styrene-cleave")
                        if installed_version != current_version:
                            # Stale copy - remove and reinstall
                            try:
                                shutil.rmtree(target_path)
                                used_copy, error = create_symlink_with_fallback(target_path, source_path)
                                if error:
                                    return InstallResult(
                                        success=False,
                                        action="error",
                                        message=error,
                                        source_path=source_path,
                                        target_path=target_path,
                                    )
                                link_type = "directory copy" if used_copy else "symlink"
                                return InstallResult(
                                    success=True,
                                    action="updated",
                                    message=f"Updated {link_type} from v{installed_version} to v{current_version}",
                                    source_path=source_path,
                                    target_path=target_path,
                                    used_copy=used_copy,
                                )
                            except Exception as e:
                                return InstallResult(
                                    success=False,
                                    action="error",
                                    message=f"Failed to update stale copy: {e}",
                                    source_path=source_path,
                                    target_path=target_path,
                                )
                    except Exception:
                        pass  # Version check failed, treat as current

                return InstallResult(
                    success=True,
                    action="skipped",
                    message=f"Skill already installed (directory copy) at {target_path}",
                    source_path=source_path,
                    target_path=target_path,
                    used_copy=True,
                )
            else:
                return InstallResult(
                    success=False,
                    action="error",
                    message=f"Directory exists at {target_path}. Remove it manually to install skill.",
                    source_path=source_path,
                    target_path=target_path,
                )
        elif target_path.is_file():
            return InstallResult(
                success=False,
                action="error",
                message=f"File exists at {target_path}. Remove it manually to install skill.",
                source_path=source_path,
                target_path=target_path,
            )

    # Create the symlink (or copy on Windows if symlink fails)
    try:
        used_copy, error = create_symlink_with_fallback(target_path, source_path)
        if error:
            return InstallResult(
                success=False,
                action="error",
                message=error,
                source_path=source_path,
                target_path=target_path,
            )
    except FileExistsError:
        # Race condition: something appeared between existence check and symlink
        return InstallResult(
            success=False,
            action="error",
            message=f"Race condition: {target_path} was created during install. Retry.",
            source_path=source_path,
            target_path=target_path,
        )

    link_type = "directory copy" if used_copy else "symlink"
    method_note = " (Windows fallback)" if used_copy else ""
    return InstallResult(
        success=True,
        action="created",
        message=f"Installed skill {link_type}{method_note}: {target_path} -> {source_path}",
        source_path=source_path,
        target_path=target_path,
        used_copy=used_copy,
    )
