"""Skills module for Fluxibly.

Provides SkillMetadata, SKILL.md parsing, and the load_skill tool schema.
Skills are Agent-layer only — progressive disclosure of instructions.
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import Any

import yaml
from pydantic import BaseModel, Field


class SkillMetadata(BaseModel):
    """Lightweight metadata extracted from SKILL.md frontmatter.

    Only ``name`` and ``description`` are loaded at discovery time.
    The full body is loaded on-demand when ``load_skill`` is called.
    """

    name: str = Field(
        ..., description="Skill identifier (e.g. 'csv-insights')"
    )
    description: str = Field(
        ..., description="Short description shown in skill catalog"
    )
    path: str = Field(
        ..., description="Path to the skill directory containing SKILL.md"
    )
    sandbox_path: str | None = Field(
        None, description="Sandbox path after staging (set by Runner)"
    )


def load_skill_metadata(name_or_path: str) -> SkillMetadata:
    """Resolve a skill identifier to its SkillMetadata.

    Parses ONLY the YAML frontmatter from SKILL.md — does NOT read the body.

    Resolution:
        1. If ``name_or_path`` looks like a path (contains ``/`` or ends with ``.md``)
           → load directly
        2. Otherwise treat as a name → search common skill directories:
           ``./skills/<name>/SKILL.md``, ``./skills/<name>.md``
        3. Not found → raise ``FileNotFoundError``

    Args:
        name_or_path: Skill name (e.g. ``"csv-insights"``) or path to skill
            directory or SKILL.md file.

    Returns:
        SkillMetadata with name, description, and resolved path.

    Raises:
        FileNotFoundError: If SKILL.md cannot be found.
        ValueError: If frontmatter is missing or invalid.
    """
    skill_md_path = _resolve_skill_path(name_or_path)
    frontmatter = _parse_frontmatter(skill_md_path)

    name = frontmatter.get("name")
    description = frontmatter.get("description", "")

    if not name:
        raise ValueError(
            f"SKILL.md at '{skill_md_path}' is missing 'name' in frontmatter."
        )

    # Path is the directory containing SKILL.md
    skill_dir = str(skill_md_path.parent)

    return SkillMetadata(name=name, description=description, path=skill_dir)


def read_skill_body(skill_path: str) -> str:
    """Read the full body of a SKILL.md file (everything after frontmatter).

    Used during Phase 2 (Activation) when ``load_skill`` tool is called.

    Args:
        skill_path: Path to the skill directory containing SKILL.md.

    Returns:
        The markdown body (everything below the closing ``---``).
    """
    skill_md = Path(skill_path) / "SKILL.md"
    if not skill_md.exists():
        raise FileNotFoundError(f"SKILL.md not found at: {skill_md}")

    return _strip_frontmatter(skill_md.read_text())


def build_load_skill_tool_schema() -> dict[str, Any]:
    """Build the ``load_skill`` function tool schema.

    This tool is auto-added to the tool list when skills are configured.
    """
    return {
        "type": "function",
        "function": {
            "name": "load_skill",
            "description": "Load a skill to get specialized instructions for a specific task.",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Name of the skill to load",
                    },
                },
                "required": ["name"],
            },
        },
    }


def build_skill_catalog(skills: dict[str, SkillMetadata]) -> str:
    """Build the skill catalog text to append to the system prompt.

    Args:
        skills: Dict of skill name → SkillMetadata.

    Returns:
        Markdown text listing available skills.
    """
    lines = [
        "",
        "## Available Skills",
        "",
        "Use the `load_skill` tool to load a skill's full instructions when the user's",
        "request would benefit from specialized procedural knowledge.",
        "",
    ]
    for skill in skills.values():
        lines.append(f"- **{skill.name}**: {skill.description}")

    return "\n".join(lines)


# ── Internal helpers ─────────────────────────────────────────


def _resolve_skill_path(name_or_path: str) -> Path:
    """Resolve a skill identifier to the SKILL.md file path."""
    # Direct path
    is_path = (
        "/" in name_or_path
        or "\\" in name_or_path
        or name_or_path.endswith(".md")
    )

    if is_path:
        p = Path(name_or_path)
        if p.is_file() and p.name == "SKILL.md":
            return p
        if p.is_dir():
            skill_md = p / "SKILL.md"
            if skill_md.exists():
                return skill_md
        # Maybe it's a direct .md file
        if p.is_file():
            return p
        raise FileNotFoundError(f"Skill not found at path: {name_or_path}")

    # Name-based search
    search_dirs = [
        Path("./skills"),
        Path("./skills/public"),
    ]
    for search_dir in search_dirs:
        # ./skills/<name>/SKILL.md
        candidate = search_dir / name_or_path / "SKILL.md"
        if candidate.exists():
            return candidate

    raise FileNotFoundError(
        f"Skill '{name_or_path}' not found. Searched: "
        f"{[str(d / name_or_path) for d in search_dirs]}"
    )


def _parse_frontmatter(path: Path) -> dict[str, Any]:
    """Parse YAML frontmatter from a SKILL.md file.

    Expects the file to start with ``---`` and end the frontmatter with ``---``.
    """
    text = path.read_text()

    # Match frontmatter block: ---\n...\n---
    match = re.match(r"^---\s*\n(.*?)\n---", text, re.DOTALL)
    if not match:
        raise ValueError(
            f"No YAML frontmatter found in '{path}'. "
            f"Expected file to start with '---' block."
        )

    try:
        data = yaml.safe_load(match.group(1))
    except yaml.YAMLError as e:
        raise ValueError(f"Invalid YAML frontmatter in '{path}': {e}") from e

    if not isinstance(data, dict):
        raise ValueError(
            f"Frontmatter in '{path}' must be a YAML mapping, got {type(data)}"
        )

    return data


def _strip_frontmatter(text: str) -> str:
    """Strip YAML frontmatter from text, returning only the body."""
    match = re.match(r"^---\s*\n.*?\n---\s*\n?", text, re.DOTALL)
    if match:
        return text[match.end() :]
    return text
