from __future__ import annotations

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

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

from .schemas import EmpresasSociosCreate, EmpresasSociosOut, EmpresasSociosUpdate

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


def _extract_rel_id(value: Any) -> Optional[str]:
    if value is None:
        return None
    if isinstance(value, str):
        return value
    if isinstance(value, dict):
        return value.get("$id") or value.get("id") or value.get("_id")
    if isinstance(value, list) and value:
        first = value[0]
        if isinstance(first, dict):
            return first.get("$id") or first.get("id") or first.get("_id")
        if isinstance(first, str):
            return first
    return None


def _map(doc: Dict[str, Any]) -> EmpresasSociosOut:
    return EmpresasSociosOut(
        id=doc.get("$id", ""),
        empresas_dados_id=_extract_rel_id(doc.get("empresas-dados")),
        nome=doc.get("nome", ""),
        qualificacao=doc.get("qualificacao", ""),
        data_entrada=doc.get("data-entrada") or None,
        faixa_etaria=doc.get("faixa-etaria") or None,
        identificador=doc.get("identificador") or None,
        created_at=doc.get("$createdAt"),
        updated_at=doc.get("$updatedAt"),
    )


class EmpresasSociosService:
    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,
        empresas_dados_id: str | None,
    ) -> Dict[str, Any]:
        limit_v = limit or _DEFAULT_LIMIT
        offset = (page - 1) * limit_v
        queries: List[Any] = [Query.limit(limit_v), Query.offset(offset)]

        if empresas_dados_id:
            queries.append(Query.equal("empresas-dados", empresas_dados_id))

        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))
        elif order and order not in _ALLOWED_ORDER_VALUES:
            raise HTTPException(400, "Ordem inválida.")

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

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

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

    async def get(self, document_id: str) -> EmpresasSociosOut:
        try:
            doc = await self._io(self._db.get_document, self._db_id, self._col, document_id)
        except AppwriteException as exc:
            if exc.code == 404:
                raise HTTPException(404, "Sócio não encontrado.") from exc
            raise HTTPException(exc.code or 500, exc.message) from exc
        return _map(doc)

    async def create(self, payload: EmpresasSociosCreate) -> EmpresasSociosOut:
        data = payload.model_dump(exclude_unset=True)
        if "empresas_dados_id" in data:
            data["empresas-dados"] = data.pop("empresas_dados_id")
        if "data_entrada" in data:
            data["data-entrada"] = data.pop("data_entrada")
        if "faixa_etaria" in data:
            data["faixa-etaria"] = data.pop("faixa_etaria")

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

    async def update(self, document_id: str, payload: EmpresasSociosUpdate) -> EmpresasSociosOut:
        updates = payload.model_dump(exclude_unset=True)
        if "empresas_dados_id" in updates:
            updates["empresas-dados"] = updates.pop("empresas_dados_id")
        if "data_entrada" in updates:
            updates["data-entrada"] = updates.pop("data_entrada")
        if "faixa_etaria" in updates:
            updates["faixa-etaria"] = updates.pop("faixa_etaria")

        try:
            doc = await self._io(self._db.update_document, self._db_id, self._col, document_id, updates)
        except AppwriteException as exc:
            if exc.code == 404:
                raise HTTPException(404, "Sócio não encontrado.") from exc
            raise HTTPException(exc.code or 500, exc.message) from exc
        return _map(doc)

    async def delete(self, document_id: str) -> None:
        try:
            await self._io(self._db.delete_document, self._db_id, self._col, document_id)
        except AppwriteException as exc:
            if exc.code == 404:
                raise HTTPException(404, "Sócio não encontrado.") from exc
            raise HTTPException(exc.code or 500, exc.message) from exc
