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, status

from src.core.auth import get_password_hash, verify_password
from src.core.config import get_settings
from .schemas import RegisterIn


_ALLOWED_NIVEIS = {"superadmin", "admin", "colaborador", "entregador"}


class AuthService:
    def __init__(self, databases: Databases, database_id: str, collection_id: str) -> None:
        self._databases = databases
        self._database_id = database_id
        self._collection_id = collection_id
        settings = get_settings()
        self._usuario_empresa_perfis_col = settings.appwrite_collection_usuario_empresa_perfis_id
        self._usuarios_perfil_col = settings.appwrite_collection_usuarios_perfil_id

    async def _run(self, func: Any, *args: Any, **kwargs: Any) -> Any:
        return await asyncio.to_thread(func, *args, **kwargs)

    async def register(self, payload: RegisterIn) -> Dict[str, Any]:
        identificador_normalized = payload.identificador.strip()
        email_normalized = payload.email.strip()
        nome_normalized = payload.nome.strip()
        senha_normalized = payload.senha.strip()
        auth_id_appwrite = (
            payload.auth_id_appwrite.strip() if isinstance(payload.auth_id_appwrite, str) else None
        )
        registro_completo = (
            bool(payload.registro_completo)
            if payload.registro_completo is not None
            else False
        )

        if not identificador_normalized:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Identificador obrigatório.")
        if not email_normalized:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="E-mail obrigatório.")
        if not nome_normalized:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Nome obrigatório.")
        if not senha_normalized:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Senha obrigatória.")

        identificador_normalized = identificador_normalized.lower()
        email_normalized = email_normalized.lower()

        try:
            existing = await self._run(
                self._databases.list_documents,
                self._database_id,
                self._collection_id,
                [Query.equal("identificador", identificador_normalized)],
            )
        except AppwriteException as exc:  # pragma: no cover - depends on Appwrite
            raise HTTPException(status_code=exc.code or status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.message) from exc

        if existing.get("total", 0):
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Usuario already exists.")

        hashed_password = get_password_hash(senha_normalized)
        is_active = True if payload.ativo is None else bool(payload.ativo)
        forced_superadmin = False
        data = {
            "identificador": identificador_normalized,
            "email": email_normalized,
            "nome": nome_normalized,
            "senha": hashed_password,
            "ativo": is_active,
            "superadmin": forced_superadmin,
            "registro-completo": registro_completo,
        }

        if auth_id_appwrite:
            data["auth-id-appwrite"] = auth_id_appwrite

        try:
            document = await self._run(
                self._databases.create_document,
                self._database_id,
                self._collection_id,
                document_id="unique()",
                data=data,
            )
        except AppwriteException as exc:  # pragma: no cover - depends on Appwrite
            if exc.code == 409:
                raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Usuario already exists.") from exc
            raise HTTPException(status_code=exc.code or status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.message) from exc

        nivel_target = (payload.nivel or "admin").lower()
        if nivel_target not in {"admin", "colaborador", "entregador"}:
            nivel_target = "admin"

        try:
            perfis_response = await self._run(
                self._databases.list_documents,
                self._database_id,
                self._usuarios_perfil_col,
                [Query.equal("codigo", nivel_target)],
            )
            perfil_docs = perfis_response.get("documents", []) or []
            perfil_id = perfil_docs[0].get("$id") if perfil_docs else None
            user_id = document.get("$id") if isinstance(document, dict) else None
            if perfil_id and user_id:
                await self._run(
                    self._databases.create_document,
                    self._database_id,
                    self._usuario_empresa_perfis_col,
                    document_id="unique()",
                    data={
                        "usuarios": user_id,
                        "usuario-perfis": perfil_id,
                    },
                )
        except Exception:
            pass

        return await self._run(self._map_user, document)

    async def authenticate(self, identificador: str, senha: str) -> Dict[str, Any]:
        identificador_normalized = identificador.strip()
        senha_normalized = senha.strip()

        if not identificador_normalized or not senha_normalized:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Credenciais inválidas.")

        try:
            result = await self._run(
                self._databases.list_documents,
                self._database_id,
                self._collection_id,
                [Query.equal("identificador", identificador_normalized)],
            )
        except AppwriteException as exc:  # pragma: no cover - depends on Appwrite
            raise HTTPException(status_code=exc.code or status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.message) from exc

        documents = result.get("documents", [])
        if not documents:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Credenciais inválidas.")

        document = documents[0]
        hashed_password = document.get("senha")
        if not isinstance(hashed_password, str):
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Credenciais inválidas.")

        if not verify_password(senha_normalized, hashed_password):
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Credenciais inválidas.")

        if not bool(document.get("ativo", True)):
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Usuário inativo.")

        return document

    async def map_user(self, document: Dict[str, Any]) -> Dict[str, Any]:
        return await self._run(self._map_user, document)

    def _normalize_nivel(self, value: Optional[str]) -> Optional[str]:
        if not isinstance(value, str):
            return None
        aliases = {
            "superadmin": "superadmin",
            "super-admin": "superadmin",
            "admin": "admin",
            "adm": "admin",
            "colaborador": "colaborador",
            "colab": "colaborador",
            "entregador": "entregador",
            "delivery": "entregador",
        }
        normalized_value = value.strip().lower()
        normalized = aliases.get(normalized_value, normalized_value)
        return normalized if normalized in _ALLOWED_NIVEIS else None

    def _pick_best(self, niveis: List[str]) -> Optional[str]:
        ranking = {"admin": 3, "colaborador": 2, "entregador": 1}
        candidatos = [nivel for nivel in niveis if nivel in ranking]
        if not candidatos:
            return None
        return max(candidatos, key=lambda item: ranking[item])

    def _nivel_from_usuario_empresa_perfis(self, user_id: str) -> Optional[str]:
        if not user_id:
            return None
        try:
            response = self._databases.list_documents(
                self._database_id,
                self._usuario_empresa_perfis_col,
                [Query.equal("usuarios", user_id)],
            )
        except Exception:
            return None

        documents = response.get("documents", []) or []
        if not documents:
            return None

        niveis: List[str] = []
        for rel in documents:
            perfil_rel = rel.get("usuario-perfis")
            if isinstance(perfil_rel, dict):
                codigo = perfil_rel.get("codigo") or perfil_rel.get("nome")
                nivel = self._normalize_nivel(codigo)
                if nivel and nivel != "superadmin":
                    niveis.append(nivel)
                continue
            if isinstance(perfil_rel, str):
                try:
                    perfil_doc = self._databases.get_document(
                        self._database_id,
                        self._usuarios_perfil_col,
                        perfil_rel,
                    )
                except Exception:
                    continue
                codigo = perfil_doc.get("codigo") or perfil_doc.get("nome")
                nivel = self._normalize_nivel(codigo)
                if nivel and nivel != "superadmin":
                    niveis.append(nivel)

        return self._pick_best(niveis)

    def _map_user(self, document: Dict[str, Any]) -> Dict[str, Any]:
        superadmin = bool(document.get("superadmin", False))
        if superadmin:
            nivel = "superadmin"
        else:
            user_id = document.get("$id", "")
            nivel = self._nivel_from_usuario_empresa_perfis(user_id) or "colaborador"

        nivel_document = document.get("nivel")
        if not superadmin:
            normalized = self._normalize_nivel(nivel_document) if isinstance(nivel_document, str) else None
            if normalized:
                nivel = normalized
            elif isinstance(nivel_document, str) and nivel_document:
                nivel = nivel_document

        return {
            "id": document.get("$id", ""),
            "identificador": document.get("identificador", ""),
            "ativo": bool(document.get("ativo", True)),
            "superadmin": superadmin,
            "auth_id_appwrite": document.get("auth-id-appwrite"),
            "nome": document.get("nome"),
            "email": document.get("email"),
            "nivel": nivel,
            "registro_completo": bool(document.get("registro-completo", False)),
            "enderecos": _ensure_list(document.get("enderecos")),
            "empresas_dados": _ensure_list(document.get("empresasDados")),
            "nome_social": document.get("nome-social"),
            "cpf": document.get("cpf"),
            "createdAt": document.get("$createdAt"),
            "updatedAt": document.get("$updatedAt"),
        }


def _ensure_list(value: Any) -> List[str]:
    if isinstance(value, list):
        return [str(item) for item in value]
    if value is None:
        return []
    return [str(value)]
