"""Skill registry — scan, search, register, and track skills."""

from __future__ import annotations

import json
import math
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any


@dataclass
class SkillInfo:
    """Metadata for a single skill."""

    name: str
    description: str
    created_at: str = ""
    times_used: int = 0
    success_count: int = 0
    fail_count: int = 0
    last_used: str = ""
    dependencies: list[str] = field(default_factory=list)
    confidence_score: float = 0.0
    input_schema: dict[str, Any] = field(default_factory=dict)
    output_schema: dict[str, Any] = field(default_factory=dict)
    sample_input: dict[str, Any] = field(default_factory=dict)
    domain: str = ""


def _wilson_score(success_count: int, fail_count: int) -> float:
    """Compute Wilson score lower bound for confidence.

    Returns 0.5 for untested skills (neutral prior).
    Penalises low sample sizes — 1/1 success ≈ 0.55, 50/50 ≈ 0.95.
    """
    total = success_count + fail_count
    if total == 0:
        return 0.5
    p = success_count / total
    z = 1.96  # 95% confidence interval
    denominator = 1 + z * z / total
    centre = (p + z * z / (2 * total)) / denominator
    spread = z * math.sqrt(p * (1 - p) / total + z * z / (4 * total * total)) / denominator
    return round(max(0.0, min(1.0, centre - spread)), 4)


class SkillRegistry:
    """Manages skills stored in ~/.alive/skills/."""

    def __init__(self, skills_dir: Path | None = None) -> None:
        from alive.config import SKILLS_DIR

        self._skills_dir = skills_dir or SKILLS_DIR
        self._skills_dir.mkdir(parents=True, exist_ok=True)

        # Install seed skills if missing
        from alive.seeds import ensure_seeds

        ensure_seeds(self._skills_dir)

        self._skills: dict[str, SkillInfo] = {}
        self._load_all()

    # ── public API ───────────────────────────────────────────────────────

    def list_all(self) -> list[SkillInfo]:
        """Return all skills sorted by usage (most-used first)."""
        return sorted(self._skills.values(), key=lambda s: s.times_used, reverse=True)

    def get_skill(self, name: str) -> SkillInfo:
        """Get a skill by name. Raises KeyError if not found."""
        if name not in self._skills:
            available = ", ".join(sorted(self._skills)) or "(none)"
            raise KeyError(f"Skill '{name}' not found. Available: {available}")
        return self._skills[name]

    def search(self, query: str) -> list[SkillInfo]:
        """Search skills by keyword matching on name, description, and SKILL.md."""
        tokens = query.lower().split()
        if not tokens:
            return self.list_all()

        scored: list[tuple[int, SkillInfo]] = []
        for info in self._skills.values():
            corpus = f"{info.name} {info.description}".lower()
            # Include SKILL.md content if it exists
            skill_md = self._skills_dir / info.name / "SKILL.md"
            if skill_md.exists():
                corpus += " " + skill_md.read_text().lower()
            score = sum(1 for t in tokens if t in corpus)
            if score > 0:
                scored.append((score, info))

        scored.sort(key=lambda x: x[0], reverse=True)
        return [info for _, info in scored]

    def register(
        self,
        name: str,
        code: str,
        description: str,
        dependencies: list[str] | None = None,
        *,
        input_schema: dict[str, Any] | None = None,
        output_schema: dict[str, Any] | None = None,
        sample_input: dict[str, Any] | None = None,
        test_code: str | None = None,
        skill_doc: str | None = None,
    ) -> SkillInfo:
        """Create a new skill on disk and add it to the registry."""
        skill_dir = self._skills_dir / name
        if skill_dir.exists():
            raise FileExistsError(
                f"Skill '{name}' already exists. Remove it first or choose a different name."
            )
        skill_dir.mkdir(parents=True)

        # Write skill.py
        (skill_dir / "skill.py").write_text(code)

        # Write test_skill.py
        (skill_dir / "test_skill.py").write_text(
            test_code
            or (
                f'"""Auto-generated tests for {name}."""\n\n'
                f"from skill import run\n\n\n"
                f"def test_{name}_runs():\n"
                f'    """Placeholder — replace with real tests."""\n'
                f"    assert callable(run)\n"
            )
        )

        # Write SKILL.md
        (skill_dir / "SKILL.md").write_text(
            skill_doc or f"# {name}\n\n{description}\n"
        )

        # Build metadata
        info = SkillInfo(
            name=name,
            description=description,
            created_at=datetime.now(timezone.utc).isoformat(),
            dependencies=dependencies or [],
            confidence_score=0.5,
            input_schema=input_schema or {},
            output_schema=output_schema or {},
            sample_input=sample_input or {},
        )
        self._save_metadata(info)
        self._skills[name] = info
        return info

    def update_stats(self, name: str, *, success: bool) -> SkillInfo:
        """Record a skill execution result and update confidence."""
        info = self.get_skill(name)
        info.times_used += 1
        if success:
            info.success_count += 1
        else:
            info.fail_count += 1
        info.last_used = datetime.now(timezone.utc).isoformat()
        info.confidence_score = _wilson_score(info.success_count, info.fail_count)
        self._save_metadata(info)
        return info

    # ── internals ────────────────────────────────────────────────────────

    def _load_all(self) -> None:
        """Scan skills_dir and load metadata from every skill."""
        if not self._skills_dir.exists():
            return
        for skill_dir in sorted(self._skills_dir.iterdir()):
            if not skill_dir.is_dir():
                continue
            meta_file = skill_dir / "metadata.json"
            if not meta_file.exists():
                continue
            try:
                data = json.loads(meta_file.read_text())
                # Only pass fields that SkillInfo knows about
                valid = {k: v for k, v in data.items() if k in SkillInfo.__dataclass_fields__}
                self._skills[data["name"]] = SkillInfo(**valid)
            except (json.JSONDecodeError, KeyError, TypeError):
                continue  # skip malformed metadata

    def _save_metadata(self, info: SkillInfo) -> None:
        """Persist a skill's metadata.json to disk."""
        meta_path = self._skills_dir / info.name / "metadata.json"
        meta_path.write_text(json.dumps(asdict(info), indent=2) + "\n")


# ── module-level factory ─────────────────────────────────────────────────────

_default_registry: SkillRegistry | None = None


def get_registry() -> SkillRegistry:
    """Get or create the default SkillRegistry instance."""
    global _default_registry
    if _default_registry is None:
        _default_registry = SkillRegistry()
    return _default_registry
