from __future__ import annotations

import asyncio
import re
from typing import Any, Dict, List

from appwrite.exception import AppwriteException
from appwrite.query import Query
from appwrite.services.databases import Databases
from fastapi import HTTPException

from .schemas import CategoriaCreate, CategoriaOut, CategoriaUpdate

_ALLOWED_SORT_FIELDS = {"nome", "slug", "$createdAt"}
_ALLOWED_ORDER_VALUES = {"asc", "desc"}
_DEFAULT_LIMIT = 20


def _slugify(v: str) -> str:
    v = re.sub(r"[^a-zA-Z0-9\s-]", "", v).strip().lower()
    return re.sub(r"[\s_-]+", "-", v)


def _map(doc: Dict[str, Any]) -> CategoriaOut:
    return CategoriaOut(
        id=doc.get("$id"),
        nome=doc.get("nome", ""),
        slug=doc.get("slug") or None,
        subcategorias=doc.get("subcategorias"),
        produtos_catalogo=doc.get("produtos_catalogo"),
        createdAt=doc.get("$createdAt", ""),
        updatedAt=doc.get("$updatedAt", ""),
    )


class CategoriasService:
    def __init__(self, databases: Databases, database_id: str, collection_id: str) -> None:
        self._db = databases
        self._db_id = database_id
        self._col = collection_id

    async def _io(self, fn: Any, *a: Any, **kw: Any) -> Any:
        return await asyncio.to_thread(fn, *a, **kw)

    async def list(
        self,
        page: int,
        limit: int | None,
        q: str | None,
        sort: str | None,
        order: str | None,
    ) -> Dict[str, Any]:
        limit_v = limit or _DEFAULT_LIMIT
        off = (page - 1) * limit_v
        queries: List[Any] = [Query.limit(limit_v), Query.offset(off)]

        if sort:
            if sort not in _ALLOWED_SORT_FIELDS:
                raise HTTPException(400, "Campo de ordenação inválido.")
            if order and order not in _ALLOWED_ORDER_VALUES:
                raise HTTPException(400, "Ordem inválida.")
            queries.append(Query.order_desc(sort) if order == "desc" else Query.order_asc(sort))

        if q and q.strip():
            s = q.strip()
            queries.append(Query.or_([Query.search("nome", s), Query.search("slug", s)]))

        try:
            res = await self._io(self._db.list_documents, self._db_id, self._col, queries)
        except AppwriteException as e:
            raise HTTPException(e.code or 500, e.message) from e

        items = [_map(d).model_dump() for d in res.get("documents", [])]
        return {"items": items, "total": res.get("total", len(items))}

    async def get(self, categoria_id: str) -> CategoriaOut:
        try:
            d = await self._io(self._db.get_document, self._db_id, self._col, categoria_id)
        except AppwriteException as e:
            if e.code == 404:
                raise HTTPException(404, "Categoria não encontrada.") from e
            raise HTTPException(e.code or 500, e.message) from e
        return _map(d)

    async def _unique(self, field: str, value: str, skip_id: str | None = None) -> None:
        try:
            res = await self._io(self._db.list_documents, self._db_id, self._col, [Query.equal(field, value)])
        except AppwriteException as e:
            raise HTTPException(e.code or 500, e.message) from e
        for d in res.get("documents", []):
            if skip_id and d.get("$id") == skip_id:
                continue
            raise HTTPException(409, f"{field} já existe.")

    async def create(self, body: CategoriaCreate) -> CategoriaOut:
        data = body.model_dump(exclude_unset=True)
        if not data.get("slug"):
            if not data.get("nome"):
                raise HTTPException(400, "nome é obrigatório.")
            data["slug"] = _slugify(data["nome"])

        if "nome" in data:
            await self._unique("nome", data["nome"])
        await self._unique("slug", data["slug"])

        try:
            d = await self._io(self._db.create_document, self._db_id, self._col, "unique()", data)
        except AppwriteException as e:
            raise HTTPException(e.code or 500, e.message) from e
        return _map(d)

    async def update(self, categoria_id: str, body: CategoriaUpdate) -> CategoriaOut:
        data = body.model_dump(exclude_unset=True)
        if "slug" in data and data["slug"]:
            await self._unique("slug", data["slug"], skip_id=categoria_id)
        if "nome" in data and data["nome"]:
            await self._unique("nome", data["nome"], skip_id=categoria_id)

        try:
            d = await self._io(self._db.update_document, self._db_id, self._col, categoria_id, data)
        except AppwriteException as e:
            if e.code == 404:
                raise HTTPException(404, "Categoria não encontrada.") from e
            raise HTTPException(e.code or 500, e.message) from e
        return _map(d)

    async def delete(self, categoria_id: str) -> None:
        try:
            await self._io(self._db.delete_document, self._db_id, self._col, categoria_id)
        except AppwriteException as e:
            if e.code == 404:
                raise HTTPException(404, "Categoria não encontrada.") from e
            raise HTTPException(e.code or 500, e.message) from e
