from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

from .builder import create_skill as build_skill
from .builder import update_skill as patch_skill
from .loader import SkillLoader, discover_skill_dirs, load_skill
from .models import LoadedSkill, SkillStatus


@dataclass(slots=True)
class ReloadSummary:
    loaded: list[str] = field(default_factory=list)
    reloaded: list[str] = field(default_factory=list)
    removed: list[str] = field(default_factory=list)
    failed: dict[str, str] = field(default_factory=dict)


class SkillRegistry:
    def __init__(self, root: str | Path):
        self.root = Path(root).expanduser().resolve()
        self.loader = SkillLoader(self.root)
        self._skills: dict[str, LoadedSkill] = {}
        self._errors: dict[str, str] = {}

    @property
    def skills(self) -> dict[str, LoadedSkill]:
        return dict(self._skills)

    @property
    def errors(self) -> dict[str, str]:
        return dict(self._errors)

    def list_status(self) -> list[SkillStatus]:
        names = set(self._skills) | set(self._errors)
        statuses: list[SkillStatus] = []
        for name in sorted(names):
            skill = self._skills.get(name)
            statuses.append(
                SkillStatus(
                    name=name,
                    enabled=bool(skill.manifest.enabled) if skill else False,
                    fingerprint=skill.fingerprint if skill else None,
                    error=self._errors.get(name),
                )
            )
        return statuses

    def get(self, name: str) -> LoadedSkill | None:
        return self._skills.get(name)

    def collect_prompt_fragments(self) -> list[str]:
        fragments: list[str] = []
        for skill in self._skills.values():
            if skill.instructions:
                fragments.append(skill.instructions)
            fragments.extend(fragment for fragment in skill.export.prompts if fragment)
        return fragments

    def collect_tools(self) -> list[dict[str, Any]]:
        tools: list[dict[str, Any]] = []
        for skill in self._skills.values():
            tools.extend(skill.export.tools)
        return tools

    def reload(self, *, context: dict[str, Any] | None = None) -> ReloadSummary:
        summary = ReloadSummary()
        seen: set[str] = set()

        for skill_dir in discover_skill_dirs(self.root):
            name = skill_dir.name
            seen.add(name)
            try:
                loaded = load_skill(skill_dir, context=context)
            except Exception as exc:
                self._skills.pop(name, None)
                self._errors[name] = str(exc)
                summary.failed[name] = str(exc)
                continue

            self._errors.pop(name, None)
            previous = self._skills.get(name)
            if previous is None:
                self._skills[name] = loaded
                summary.loaded.append(name)
                continue

            if previous.fingerprint != loaded.fingerprint:
                self._skills[name] = loaded
                summary.reloaded.append(name)

        removed_names = sorted(set(self._skills).difference(seen))
        for name in removed_names:
            self._skills.pop(name, None)
            self._errors.pop(name, None)
            summary.removed.append(name)

        removed_errors = set(self._errors).difference(seen)
        for name in removed_errors:
            self._errors.pop(name, None)

        return summary


class SkillManager:
    def __init__(self, root: str | Path):
        self.registry = SkillRegistry(root)

    @property
    def root(self) -> Path:
        return self.registry.root

    def load_all(self, *, context: dict[str, Any] | None = None) -> list[LoadedSkill]:
        self.registry = SkillRegistry(self.root)
        self.registry.reload(context=context)
        return list(self.registry.skills.values())

    def reload_if_needed(self, *, context: dict[str, Any] | None = None) -> ReloadSummary:
        return self.registry.reload(context=context)

    def list_skills(self) -> list[SkillStatus]:
        return self.registry.list_status()

    def get_system_prompt_fragments(self) -> list[str]:
        return self.registry.collect_prompt_fragments()

    def get_tools(self) -> list[dict[str, Any]]:
        return self.registry.collect_tools()

    def get(self, name: str) -> LoadedSkill | None:
        return self.registry.get(name)

    def create_skill(
        self,
        *,
        name: str,
        description: str,
        instructions: str | None = None,
        entrypoint_code: str | None = None,
        with_entrypoint: bool = True,
        context: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        skill_dir = build_skill(
            self.root,
            name=name,
            description=description,
            instructions=instructions,
            entrypoint_code=entrypoint_code,
            with_entrypoint=with_entrypoint,
        )
        summary = self.reload_if_needed(context=context)
        return {
            "skill_dir": str(skill_dir),
            "reload": {
                "loaded": summary.loaded,
                "reloaded": summary.reloaded,
                "removed": summary.removed,
                "failed": summary.failed,
            },
        }

    def update_skill(
        self,
        *,
        name: str,
        description: str | None = None,
        instructions: str | None = None,
        entrypoint_code: str | None = None,
        manifest_updates: dict[str, Any] | None = None,
        context: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        result = patch_skill(
            self.root / name,
            description=description,
            instructions=instructions,
            entrypoint_code=entrypoint_code,
            manifest_updates=manifest_updates,
        )
        summary = self.reload_if_needed(context=context)
        return {
            "skill_dir": str(result["skill_dir"]),
            "backup_dir": str(result["backup_dir"]) if result["backup_dir"] else None,
            "reload": {
                "loaded": summary.loaded,
                "reloaded": summary.reloaded,
                "removed": summary.removed,
                "failed": summary.failed,
            },
        }
