"""Skills resource -- CRUD, catalog, upload, and server attachment."""

from __future__ import annotations

import builtins
from pathlib import Path
from typing import Any
from urllib.parse import quote
from uuid import UUID

from mcpgateway_sdk._http import HTTPClient
from mcpgateway_sdk.models.skill import AttachResult, Skill, SkillCatalogEntry

_ENDPOINTS = {
    "list": ("GET", "/api/v1/skills"),
    "get": ("GET", "/api/v1/skills/{skill_id}"),
    "create": ("POST", "/api/v1/skills"),
    "update": ("PATCH", "/api/v1/skills/{skill_id}"),
    "delete": ("DELETE", "/api/v1/skills/{skill_id}"),
    "upload": ("POST", "/api/v1/skills/upload"),
    "templates": ("GET", "/api/v1/skills/templates"),
    "catalog": ("GET", "/api/v1/skills/catalog/{source}"),
    "catalog_detail": ("GET", "/api/v1/skills/catalog/{source}/{skill_name}"),
    "import_from_catalog": ("POST", "/api/v1/skills/import"),
    "download_package": ("GET", "/api/v1/skills/{skill_id}/package"),
    "attach": ("POST", "/api/v1/servers/{server_id}/skills/{skill_id}/attach"),
    "detach": ("DELETE", "/api/v1/servers/{server_id}/skills/{skill_id}/detach"),
}

# Type alias to avoid shadowing builtin ``list`` inside the class.
_ListDictResult = builtins.list[dict[str, Any]]


class SkillsResource:
    """Interact with skills managed by the MCP Gateway."""

    _endpoints = _ENDPOINTS

    def __init__(self, http: HTTPClient) -> None:
        self._http = http

    # ------------------------------------------------------------------
    # CRUD
    # ------------------------------------------------------------------

    async def list(
        self,
        search: str | None = None,
        source_type: str | None = None,
        server_id: str | UUID | None = None,
        offset: int = 0,
        limit: int = 100,
    ) -> builtins.list[Skill]:
        """List skills with optional filters.

        Args:
            search: Free-text search string.
            source_type: Filter by source type (e.g. ``"catalog"``).
            server_id: Filter by MCP server ID.
            offset: Pagination offset.
            limit: Maximum number of results.

        Returns:
            A list of Skill models.
        """
        params: dict[str, Any] = {"offset": offset, "limit": limit}
        if search is not None:
            params["search"] = search
        if source_type is not None:
            params["source_type"] = source_type
        if server_id is not None:
            params["mcp_server_id"] = str(server_id)

        data = await self._http.request("GET", "/api/v1/skills", params=params)
        return [Skill.model_validate(item) for item in data.get("items", [])]

    async def get(self, skill_id: str | UUID) -> Skill:
        """Get a single skill by ID.

        Args:
            skill_id: The skill UUID.

        Returns:
            The Skill model.
        """
        data = await self._http.request("GET", f"/api/v1/skills/{skill_id}")
        return Skill.model_validate(data)

    async def create(
        self,
        name: str,
        skill_md_content: str,
        description: str | None = None,
        server_id: str | UUID | None = None,
        skill_metadata: dict[str, Any] | None = None,
    ) -> Skill:
        """Create a new skill.

        Args:
            name: Skill name.
            skill_md_content: Markdown content for the skill.
            description: Optional description.
            server_id: Optional MCP server to link.
            skill_metadata: Optional metadata dict.

        Returns:
            The created Skill model.
        """
        body: dict[str, Any] = {
            "name": name,
            "skill_md_content": skill_md_content,
        }
        if description is not None:
            body["description"] = description
        if server_id is not None:
            body["mcp_server_id"] = str(server_id)
        if skill_metadata is not None:
            body["skill_metadata"] = skill_metadata

        data = await self._http.request("POST", "/api/v1/skills", json=body)
        return Skill.model_validate(data)

    async def update(
        self,
        skill_id: str | UUID,
        *,
        name: str | None = None,
        description: str | None = None,
        skill_md_content: str | None = None,
        server_id: str | UUID | None = None,
        skill_metadata: dict[str, Any] | None = None,
        clear_server_link: bool = False,
        portal_visible: bool | None = None,
        portal_category: str | None = None,
        portal_tags: builtins.list[str] | None = None,
    ) -> Skill:
        """Partially update a skill. Only non-None fields are sent.

        Args:
            skill_id: The skill UUID.
            name: New name.
            description: New description.
            skill_md_content: New markdown content.
            server_id: New MCP server link.
            skill_metadata: New metadata.
            clear_server_link: If True, send ``mcp_server_id: null`` to unlink.
            portal_visible: Portal visibility flag.
            portal_category: Portal category string.
            portal_tags: Portal tags list.

        Returns:
            The updated Skill model.

        Raises:
            ValueError: If both ``server_id`` and ``clear_server_link`` are given.
        """
        if server_id is not None and clear_server_link:
            raise ValueError("Cannot pass both server_id and clear_server_link=True")

        body: dict[str, Any] = {}
        if name is not None:
            body["name"] = name
        if description is not None:
            body["description"] = description
        if skill_md_content is not None:
            body["skill_md_content"] = skill_md_content
        if server_id is not None:
            body["mcp_server_id"] = str(server_id)
        if skill_metadata is not None:
            body["skill_metadata"] = skill_metadata
        if clear_server_link:
            body["clear_server_link"] = True
            body["mcp_server_id"] = None
        if portal_visible is not None:
            body["portal_visible"] = portal_visible
        if portal_category is not None:
            body["portal_category"] = portal_category
        if portal_tags is not None:
            body["portal_tags"] = portal_tags

        data = await self._http.request("PATCH", f"/api/v1/skills/{skill_id}", json=body)
        return Skill.model_validate(data)

    async def delete(self, skill_id: str | UUID) -> None:
        """Delete a skill.

        Args:
            skill_id: The skill UUID.
        """
        await self._http.request("DELETE", f"/api/v1/skills/{skill_id}")

    # ------------------------------------------------------------------
    # Upload
    # ------------------------------------------------------------------

    async def upload(
        self,
        file_path: str | Path | None = None,
        content: bytes | None = None,
        filename: str | None = None,
        server_id: str | UUID | None = None,
    ) -> Skill:
        """Upload a skill file.

        Provide either ``file_path`` or both ``content`` and ``filename``.

        Args:
            file_path: Path to a local file to upload.
            content: Raw bytes of the file content.
            filename: Filename when using ``content`` directly.
            server_id: Optional MCP server to link the skill to.

        Returns:
            The created Skill model.

        Raises:
            ValueError: If neither ``file_path`` nor ``content``/``filename`` is given.
        """
        if file_path is not None:
            path = Path(file_path)
            content = path.read_bytes()
            filename = path.name
        elif content is not None:
            if filename is None:
                raise ValueError("filename is required when providing content directly")
        else:
            raise ValueError("Provide either file_path or content + filename")

        files = {"file": (filename, content, "application/octet-stream")}
        data_fields: dict[str, str] | None = None
        if server_id is not None:
            data_fields = {"mcp_server_id": str(server_id)}

        data = await self._http.request(
            "POST", "/api/v1/skills/upload", files=files, data=data_fields
        )
        return Skill.model_validate(data)

    # ------------------------------------------------------------------
    # Templates & Catalog
    # ------------------------------------------------------------------

    async def templates(self) -> _ListDictResult:
        """List available skill templates.

        Returns:
            A list of template dicts.
        """
        data: Any = await self._http.request("GET", "/api/v1/skills/templates")
        if isinstance(data, builtins.list):
            return data
        return builtins.list(data.get("items", []))

    async def catalog(
        self, source: str, search: str | None = None
    ) -> builtins.list[SkillCatalogEntry]:
        """List skills from a catalog source.

        Args:
            source: Catalog source (e.g. ``"builtin"``, a GitHub URL, etc.).
            search: Optional search filter.

        Returns:
            A list of SkillCatalogEntry models.
        """
        params: dict[str, str] = {}
        if search is not None:
            params["search"] = search

        encoded_source = quote(source, safe="")
        data = await self._http.request(
            "GET", f"/api/v1/skills/catalog/{encoded_source}", params=params
        )
        return [SkillCatalogEntry.model_validate(item) for item in data.get("items", [])]

    async def catalog_detail(self, source: str, skill_name: str) -> SkillCatalogEntry:
        """Get detailed info for a single catalog entry.

        Args:
            source: Catalog source.
            skill_name: Name of the skill in the catalog.

        Returns:
            The SkillCatalogEntry model.
        """
        encoded_source = quote(source, safe="")
        data = await self._http.request(
            "GET", f"/api/v1/skills/catalog/{encoded_source}/{skill_name}"
        )
        return SkillCatalogEntry.model_validate(data)

    async def import_from_catalog(
        self,
        source: str,
        skill_name: str,
        server_id: str | UUID | None = None,
        github_repo: str | None = None,
    ) -> Skill:
        """Import a skill from the catalog.

        Args:
            source: Catalog source.
            skill_name: Name of the skill to import.
            server_id: Optional MCP server to link.
            github_repo: Optional GitHub repo for GitHub-sourced skills.

        Returns:
            The imported Skill model.
        """
        body: dict[str, Any] = {"source": source, "skill_name": skill_name}
        if server_id is not None:
            body["mcp_server_id"] = str(server_id)
        if github_repo is not None:
            body["github_repo"] = github_repo

        data = await self._http.request("POST", "/api/v1/skills/import", json=body)
        return Skill.model_validate(data)

    # ------------------------------------------------------------------
    # Package download
    # ------------------------------------------------------------------

    async def download_package(self, skill_id: str | UUID) -> bytes:
        """Download the packaged skill archive.

        Args:
            skill_id: The skill UUID.

        Returns:
            Raw bytes of the skill package.
        """
        return await self._http.request_raw("GET", f"/api/v1/skills/{skill_id}/package")

    # ------------------------------------------------------------------
    # Server attachment
    # ------------------------------------------------------------------

    async def attach(self, skill_id: str | UUID, server_id: str | UUID) -> AttachResult:
        """Attach a skill to an MCP server.

        Args:
            skill_id: The skill UUID.
            server_id: The server UUID.

        Returns:
            An AttachResult with the attachment details.
        """
        data = await self._http.request(
            "POST",
            f"/api/v1/servers/{server_id}/skills/{skill_id}/attach",
        )
        return AttachResult.model_validate(data)

    async def detach(self, skill_id: str | UUID, server_id: str | UUID) -> None:
        """Detach a skill from an MCP server.

        Args:
            skill_id: The skill UUID.
            server_id: The server UUID.
        """
        await self._http.request(
            "DELETE",
            f"/api/v1/servers/{server_id}/skills/{skill_id}/detach",
        )
