from __future__ import annotations

import asyncio
from datetime import date
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, status
import httpx

from src.core.appwrite_endpoint import normalize_appwrite_endpoint
from src.core.appwrite_session import normalize_appwrite_session_cookie

from .schemas import FaturaCreate, FaturaOut, FaturaUpdate

_ALLOWED_SORT_FIELDS = {"data-vencimento", "$createdAt"}  # chaves Appwrite
_ALLOWED_ORDER_VALUES = {"asc", "desc"}
_DEFAULT_LIMIT = 20


def _map(doc: Dict[str, Any]) -> FaturaOut:
    dv = doc.get("data-vencimento")  # pode vir None ou string ISO
    return FaturaOut(
        id=doc.get("$id"),
        data_vencimento=dv if dv else None,
        status=doc.get("status", ""),
        pedidos=doc.get("pedidos"),
        empresas=doc.get("empresas"),
        createdAt=doc.get("$createdAt", ""),
        updatedAt=doc.get("$updatedAt", ""),
    )


class FaturasService:
    def __init__(
        self,
        databases: Databases,
        database_id: str,
        collection_id: str,
        *,
        project_id: str | None = None,
        endpoint: str | None = None,
        session_cookie: str | None = None,
        api_key: str | None = None,
    ) -> None:
        self._db = databases
        self._db_id = database_id
        self._col = collection_id
        self._project_id = project_id.strip() if isinstance(project_id, str) else None
        self._endpoint = normalize_appwrite_endpoint(endpoint, database_id)
        self._session_cookie = normalize_appwrite_session_cookie(self._project_id, session_cookie)
        self._api_key = api_key.strip() if isinstance(api_key, str) else None

    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,
        status: 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 status:
            queries.append(Query.equal("status", status))

        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()
            # não há campo 'nome'; pesquisar em status
            queries.append(Query.search("status", 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, fatura_id: str) -> FaturaOut:
        try:
            d = await self._io(self._db.get_document, self._db_id, self._col, fatura_id)
        except AppwriteException as e:
            if e.code == 404:
                raise HTTPException(404, "Fatura não encontrada.") from e
            raise HTTPException(e.code or 500, e.message) from e
        return _map(d)

    async def create(self, body: FaturaCreate) -> FaturaOut:
        data = body.model_dump(exclude_unset=True)
        # mapear snake_case -> Appwrite
        if "data_vencimento" in data:
            dv: date | None = data.pop("data_vencimento")
            data["data-vencimento"] = dv.isoformat() if dv else None

        if data.get("pedidos") is None:
            data.pop("pedidos", None)

        if self._project_id and self._endpoint and (self._session_cookie or self._api_key):
            return await self._create_via_http(data)

        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 _create_via_http(self, data: Dict[str, Any]) -> FaturaOut:
        base_endpoint = (self._endpoint or "").rstrip("/")
        url = f"{base_endpoint}/databases/{self._db_id}/collections/{self._col}/documents"

        headers = {
            "X-Appwrite-Project": self._project_id or "",
            "Accept": "application/json",
            "Content-Type": "application/json",
        }

        if self._session_cookie:
            headers["Cookie"] = self._session_cookie
        if self._api_key:
            headers["X-Appwrite-Key"] = self._api_key

        payload = {"documentId": "unique()", "data": data}

        try:
            async with httpx.AsyncClient(timeout=30.0) as client:
                response = await client.post(url, headers=headers, json=payload)
        except httpx.HTTPError as exc:
            raise HTTPException(
                status_code=status.HTTP_502_BAD_GATEWAY,
                detail="Falha ao se comunicar com Appwrite.",
            ) from exc

        if response.status_code >= 400:
            detail: Any = None
            try:
                body = response.json()
                if isinstance(body, dict):
                    detail = body.get("message") or body.get("error") or body.get("errors") or body
                else:
                    detail = body
            except ValueError:
                detail = response.text or "Erro desconhecido no Appwrite."

            raise HTTPException(status_code=response.status_code, detail=detail)

        try:
            document = response.json()
        except ValueError as exc:  # pragma: no cover - proteção adicional
            raise HTTPException(
                status_code=status.HTTP_502_BAD_GATEWAY,
                detail="Resposta inválida do Appwrite.",
            ) from exc

        if not isinstance(document, dict):
            raise HTTPException(
                status_code=status.HTTP_502_BAD_GATEWAY,
                detail="Resposta inválida do Appwrite.",
            )

        return _map(document)

    async def update(self, fatura_id: str, body: FaturaUpdate) -> FaturaOut:
        data = body.model_dump(exclude_unset=True)
        if "data_vencimento" in data:
            dv = data.pop("data_vencimento")
            data["data-vencimento"] = dv.isoformat() if dv else None

        if data.get("pedidos") is None:
            data.pop("pedidos", None)

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

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