"""Skill installer — install, uninstall, search, list."""

from __future__ import annotations

import json
import shutil
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path

from icarus.skills.registry import (
    RegistryError,
    SkillIndexEntry,
    fetch_index,
    fetch_skill_files,
)
from icarus.skills.registry_config import RegistryEntry, SkillsConfig
from icarus.skills.validation import validate_skill_name, validate_skill_path

_MANIFEST = ".registry.json"


def _user_skills_dir() -> Path:
    from icarus.config.app_config import Settings

    return Settings.from_environment().get_user_skills_dir("default")


@dataclass
class SearchResult:
    name: str
    description: str
    version: str
    tags: list[str]
    registry: str


@dataclass
class InstalledSkill:
    name: str
    version: str
    registry: str
    installed_at: str


def search_skills(
    query: str, registries: list[RegistryEntry] | None = None
) -> list[SearchResult]:
    """Search all registries for skills matching query."""
    if registries is None:
        registries = SkillsConfig.load().registries

    q = query.lower()
    results: list[SearchResult] = []
    for entry in registries:
        for s in fetch_index(entry):
            if _matches(s, q):
                results.append(SearchResult(
                    s.name, s.description, s.version, s.tags, entry.name,
                ))
    results.sort(key=lambda r: r.name)
    return results


def install_skill(
    name: str,
    registry: RegistryEntry | None = None,
    *,
    known_version: str = "",
    plugins_base: Path | None = None,
) -> InstalledSkill:
    """Install a skill by name from a specific or first-match registry.

    Args:
        name: Skill name to install.
        registry: Specific registry to install from. If None, searches all.
        known_version: If provided, skip the fetch_index call to look up the
            version. Use this when the caller already has the version from a
            prior fetch_index (avoids redundant HTTP round-trips).
    """
    ok, err = validate_skill_name(name)
    if not ok:
        raise ValueError(err)

    entries = [registry] if registry else SkillsConfig.load().registries

    files = None
    source: RegistryEntry | None = None
    version = known_version
    for entry in entries:
        try:
            entry_version = known_version
            # Only fetch index if we don't already have the version
            if not entry_version:
                for s in fetch_index(entry):
                    if s.name == name:
                        entry_version = s.version
                        break
            files = fetch_skill_files(entry, name)
            source = entry
            version = entry_version
            break
        except RegistryError:
            continue

    if files is None or source is None:
        msg = f"Skill '{name}' not found in any registry"
        raise RegistryError(msg)

    # Write to plugin-specific directory (not user skills dir)
    if plugins_base is not None:
        skills_dir = plugins_base / source.name / "skills"
        skills_dir.mkdir(parents=True, exist_ok=True)
    else:
        from icarus.config.app_config import Settings
        skills_dir = Settings.ensure_plugin_skills_dir(source.name)
    skill_dir = skills_dir / name
    ok, err = validate_skill_path(skill_dir, skills_dir)
    if not ok:
        raise ValueError(err)

    skill_dir.mkdir(parents=True, exist_ok=True)
    for fname, content in files.items():
        dest = skill_dir / fname
        if not dest.resolve().is_relative_to(skill_dir.resolve()):
            continue
        dest.parent.mkdir(parents=True, exist_ok=True)
        dest.write_bytes(content)

    now = datetime.now(timezone.utc).isoformat()
    (skill_dir / _MANIFEST).write_text(json.dumps({
        "registry": source.name,
        "version": version,
        "installed_at": now,
        "source_url": source.url,
    }), encoding="utf-8")

    return InstalledSkill(name, version, source.name, now)


def uninstall_skill(name: str, plugin: str | None = None) -> None:
    """Remove an installed skill directory.

    Args:
        name: Skill name.
        plugin: If provided, remove from that plugin's directory.
            Otherwise search plugin dirs then fall back to user dir.
    """
    ok, err = validate_skill_name(name)
    if not ok:
        raise ValueError(err)

    from icarus.config.app_config import Settings

    if plugin:
        skill_dir = Settings.get_plugin_skills_dir(plugin) / name
    else:
        # Search plugin directories first
        skill_dir = _find_installed_skill(name)
        if not skill_dir:
            skill_dir = _user_skills_dir() / name

    if not skill_dir.exists():
        msg = f"Skill '{name}' is not installed"
        raise ValueError(msg)

    shutil.rmtree(skill_dir)


def _scan_manifests(
    skills_dir: Path, *, default_registry: str = "",
) -> list[InstalledSkill]:
    """Scan a skills directory for installed skills with .registry.json."""
    results: list[InstalledSkill] = []
    if not skills_dir.exists():
        return results
    for d in sorted(skills_dir.iterdir()):
        manifest = d / _MANIFEST
        if not d.is_dir() or not manifest.exists():
            continue
        try:
            m = json.loads(manifest.read_text(encoding="utf-8"))
            results.append(InstalledSkill(
                d.name, m.get("version", ""),
                m.get("registry", default_registry),
                m.get("installed_at", ""),
            ))
        except (json.JSONDecodeError, OSError):
            continue
    return results


def list_installed() -> list[InstalledSkill]:
    """List skills installed from registries (have .registry.json).

    Scans both the legacy user skills dir and per-plugin directories.
    """
    from icarus.config.app_config import Settings

    results: list[InstalledSkill] = []

    # Scan per-plugin directories
    plugins_base = Settings.get_plugins_base_dir()
    if plugins_base.exists():
        for plugin_dir in sorted(plugins_base.iterdir()):
            skills_dir = plugin_dir / "skills"
            if skills_dir.is_dir():
                results.extend(
                    _scan_manifests(skills_dir, default_registry=plugin_dir.name)
                )

    # Also scan legacy user skills dir for backwards compat
    results.extend(_scan_manifests(_user_skills_dir()))

    return results


def _find_installed_skill(name: str) -> Path | None:
    """Find a skill across all plugin directories."""
    from icarus.config.app_config import Settings

    plugins_base = Settings.get_plugins_base_dir()
    if plugins_base.exists():
        for plugin_dir in plugins_base.iterdir():
            skill_dir = plugin_dir / "skills" / name
            if skill_dir.exists():
                return skill_dir
    return None


def _matches(skill: SkillIndexEntry, query: str) -> bool:
    if not query:
        return True
    return (
        query in skill.name.lower()
        or query in skill.description.lower()
        or any(query in t.lower() for t in skill.tags)
    )
