from __future__ import annotations

import asyncio
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 UsuarioPerfilCreate, UsuarioPerfilOut, UsuarioPerfilUpdate

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


def _map(doc: Dict[str, Any]) -> UsuarioPerfilOut:
    return UsuarioPerfilOut(
        id=doc.get("$id"),
        codigo=doc.get("codigo", ""),
        nome=doc.get("nome", ""),
        createdAt=doc.get("$createdAt", ""),
        updatedAt=doc.get("$updatedAt", ""),
    )


class UsuariosPerfilService:
    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, *args: Any, **kwargs: Any) -> Any:
        return await asyncio.to_thread(fn, *args, **kwargs)

    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(status_code=400, detail="Campo de ordenação inválido.")
            if order and order not in _ALLOWED_ORDER_VALUES:
                raise HTTPException(status_code=400, detail="Ordem inválida.")
            queries.append(Query.order_desc(sort) if order == "desc" else Query.order_asc(sort))

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

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

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

    async def get(self, perfil_id: str) -> UsuarioPerfilOut:
        try:
            doc = await self._io(self._db.get_document, self._db_id, self._col, perfil_id)
        except AppwriteException as e:
            if e.code == 404:
                raise HTTPException(status_code=404, detail="Perfil de usuário não encontrado.") from e
            raise HTTPException(status_code=e.code or 500, detail=e.message) from e
        return _map(doc)

    async def _ensure_unique_codigo(self, codigo: str, *, skip_id: str | None = None) -> None:
        try:
            res = await self._io(
                self._db.list_documents,
                self._db_id,
                self._col,
                [Query.equal("codigo", codigo)],
            )
        except AppwriteException as e:
            raise HTTPException(status_code=e.code or 500, detail=e.message) from e
        for doc in res.get("documents", []):
            if skip_id and doc.get("$id") == skip_id:
                continue
            raise HTTPException(status_code=409, detail="Código de perfil já cadastrado.")

    async def create(self, payload: UsuarioPerfilCreate) -> UsuarioPerfilOut:
        data = payload.model_dump(exclude_unset=True)
        codigo = data.get("codigo")
        if isinstance(codigo, str):
            codigo = codigo.strip()
            if not codigo:
                raise HTTPException(status_code=400, detail="Código é obrigatório.")
            data["codigo"] = codigo
        await self._ensure_unique_codigo(data["codigo"])
        try:
            doc = await self._io(self._db.create_document, self._db_id, self._col, "unique()", data)
        except AppwriteException as e:
            raise HTTPException(status_code=e.code or 500, detail=e.message) from e
        return _map(doc)

    async def update(self, perfil_id: str, payload: UsuarioPerfilUpdate) -> UsuarioPerfilOut:
        updates = payload.model_dump(exclude_unset=True)
        if "codigo" in updates and updates["codigo"] is not None:
            codigo = updates["codigo"].strip()
            if not codigo:
                raise HTTPException(status_code=400, detail="Código é obrigatório.")
            await self._ensure_unique_codigo(codigo, skip_id=perfil_id)
            updates["codigo"] = codigo
        if "nome" in updates and updates["nome"] is not None:
            nome = updates["nome"].strip()
            if not nome:
                raise HTTPException(status_code=400, detail="Nome é obrigatório.")
            updates["nome"] = nome
        try:
            doc = await self._io(
                self._db.update_document,
                self._db_id,
                self._col,
                perfil_id,
                updates,
            )
        except AppwriteException as e:
            if e.code == 404:
                raise HTTPException(status_code=404, detail="Perfil de usuário não encontrado.") from e
            raise HTTPException(status_code=e.code or 500, detail=e.message) from e
        return _map(doc)

    async def delete(self, perfil_id: str) -> None:
        try:
            await self._io(self._db.delete_document, self._db_id, self._col, perfil_id)
        except AppwriteException as e:
            if e.code == 404:
                raise HTTPException(status_code=404, detail="Perfil de usuário não encontrado.") from e
            raise HTTPException(status_code=e.code or 500, detail=e.message) from e
