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 UsuarioDataCreate, UsuarioDataOut, UsuarioDataUpdate

_ALLOWED_SORT_FIELDS = {"nome-completo", "cpf", "$createdAt"}
_ALLOWED_ORDER_VALUES = {"asc", "desc"}
_DEFAULT_LIMIT = 20


def _map(doc: Dict[str, Any]) -> UsuarioDataOut:
    return UsuarioDataOut(
        id=doc.get("$id"),
        nome_completo=doc.get("nome-completo", ""),
        nome_social=doc.get("nome-social"),
        cpf=doc.get("cpf"),
        enderecos=doc.get("enderecos", []) or [],
        email=doc.get("email"),
        createdAt=doc.get("$createdAt", ""),
        updatedAt=doc.get("$updatedAt", ""),
    )


class UsuariosDataService:
    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("nome-completo", term),
                        Query.search("nome-social", term),
                        Query.search("cpf", 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(by_alias=True) for d in docs]
        return {"items": items, "total": res.get("total", len(items))}

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

    async def create(self, payload: UsuarioDataCreate) -> UsuarioDataOut:
        data = payload.model_dump(by_alias=True, exclude_unset=True)
        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, usuario_data_id: str, payload: UsuarioDataUpdate) -> UsuarioDataOut:
        updates = payload.model_dump(by_alias=True, exclude_unset=True)
        try:
            doc = await self._io(
                self._db.update_document,
                self._db_id,
                self._col,
                usuario_data_id,
                updates,
            )
        except AppwriteException as e:
            if e.code == 404:
                raise HTTPException(status_code=404, detail="Dados do usuário não encontrados.") from e
            raise HTTPException(status_code=e.code or 500, detail=e.message) from e
        return _map(doc)

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