"""
Listagens auxiliares da API KeepEdu (usuários e unidades).
"""
from __future__ import annotations

import logging
import time
from typing import Any

import requests

from app.settings import (
    KEEPEDU_API_KEY,
    KEEPEDU_IMPORTAR_TIMEOUT_SECONDS,
    KEEPEDU_INSTITUTE,
    KEEPEDU_LISTAR_SERIES_URL,
    KEEPEDU_LISTAR_TURMAS_URL,
    KEEPEDU_LISTAR_UNIDADES_URL,
    KEEPEDU_LISTAR_USUARIOS_URL,
)

logger = logging.getLogger(__name__)

_CACHE_TTL_SECONDS = 300
_cache: dict[str, Any] = {
    "usuarios_ts": 0.0,
    "usuarios": [],
    "usuarios_erro": "",
    "unidades_ts": 0.0,
    "unidades": [],
    "unidades_erro": "",
    "series": {},
    "turmas": {},
}


def _cabecalhos_keepedu() -> dict[str, str]:
    return {
        "ApiKey": KEEPEDU_API_KEY,
        "Institute": KEEPEDU_INSTITUTE,
        "Content-Type": "application/json",
        "Accept": "application/json",
    }


def _normalizar_nome(texto: Any) -> str:
    return str(texto or "").strip().strip('"').strip()


def _item_ativo(item: dict[str, Any]) -> bool:
    status = item.get("status")
    if status is not None:
        try:
            return int(status) == 1
        except (TypeError, ValueError):
            pass
    status_texto = str(item.get("status_texto") or "").strip().lower()
    return status_texto in {"ativo", "active", "enabled", "1"}


def _extrair_lista(corpo: dict[str, Any], chaves: tuple[str, ...]) -> list[dict[str, Any]]:
    for chave in chaves:
        valor = corpo.get(chave)
        if isinstance(valor, list):
            return [item for item in valor if isinstance(item, dict)]
    return []


def _post_listagem(
    url: str,
    rotulo: str,
    payload: dict[str, Any] | None = None,
) -> tuple[dict[str, Any] | None, str]:
    if not url:
        return None, f"{rotulo}: URL não configurada."

    if not KEEPEDU_API_KEY or not KEEPEDU_INSTITUTE:
        return None, "Configure KEEPEDU_API_KEY e KEEPEDU_INSTITUTE no .env."

    try:
        resposta = requests.post(
            url,
            json=payload if payload is not None else {},
            headers=_cabecalhos_keepedu(),
            timeout=KEEPEDU_IMPORTAR_TIMEOUT_SECONDS,
        )
    except requests.exceptions.Timeout:
        return None, f"Tempo limite excedido ao consultar {rotulo}."
    except requests.exceptions.RequestException as exc:
        return None, f"Erro ao consultar {rotulo}: {exc}"

    try:
        corpo = resposta.json()
    except ValueError:
        return None, f"Resposta inválida ao consultar {rotulo} (HTTP {resposta.status_code})."

    if not isinstance(corpo, dict):
        return None, f"Resposta inesperada ao consultar {rotulo}."

    if resposta.status_code >= 400 or corpo.get("success") is False:
        mensagem = str(
            corpo.get("message")
            or corpo.get("mensagem")
            or corpo.get("erro")
            or f"HTTP {resposta.status_code}"
        ).strip()
        return None, mensagem or f"Falha ao consultar {rotulo}."

    return corpo, ""


def listar_usuarios_keepedu(*, force: bool = False) -> dict[str, Any]:
    agora = time.time()
    if (
        not force
        and _cache.get("usuarios")
        and (agora - float(_cache.get("usuarios_ts") or 0)) < _CACHE_TTL_SECONDS
    ):
        return {
            "success": True,
            "usuarios": list(_cache["usuarios"]),
            "total": len(_cache["usuarios"]),
            "erro": _cache.get("usuarios_erro", ""),
            "cache": True,
        }

    corpo, erro = _post_listagem(KEEPEDU_LISTAR_USUARIOS_URL, "usuários")
    if erro or not corpo:
        return {"success": False, "usuarios": [], "total": 0, "erro": erro, "cache": False}

    brutos = _extrair_lista(corpo, ("usuarios", "data", "items"))
    usuarios: list[dict[str, str]] = []
    vistos: set[str] = set()

    for bruto in brutos:
        if not _item_ativo(bruto):
            continue
        nome = _normalizar_nome(bruto.get("nome"))
        if not nome or nome in vistos:
            continue
        vistos.add(nome)
        usuarios.append(
            {
                "id": str(bruto.get("id") or "").strip(),
                "nome": nome,
                "email": str(bruto.get("email") or "").strip(),
                "perfil": str(bruto.get("perfil") or "").strip(),
            }
        )

    usuarios.sort(key=lambda item: item["nome"].lower())

    _cache.update(
        {
            "usuarios_ts": agora,
            "usuarios": usuarios,
            "usuarios_erro": "",
        }
    )

    return {
        "success": True,
        "usuarios": usuarios,
        "total": len(usuarios),
        "erro": "",
        "cache": False,
    }


def listar_unidades_keepedu(*, force: bool = False) -> dict[str, Any]:
    agora = time.time()
    if (
        not force
        and _cache.get("unidades")
        and (agora - float(_cache.get("unidades_ts") or 0)) < _CACHE_TTL_SECONDS
    ):
        return {
            "success": True,
            "unidades": list(_cache["unidades"]),
            "total": len(_cache["unidades"]),
            "erro": _cache.get("unidades_erro", ""),
            "cache": True,
        }

    corpo, erro = _post_listagem(KEEPEDU_LISTAR_UNIDADES_URL, "unidades")
    if erro or not corpo:
        return {"success": False, "unidades": [], "total": 0, "erro": erro, "cache": False}

    brutos = _extrair_lista(corpo, ("unidades", "data", "items"))
    unidades: list[dict[str, str]] = []
    vistos: set[str] = set()

    for bruto in brutos:
        if not _item_ativo(bruto):
            continue
        nome = _normalizar_nome(bruto.get("nome"))
        if not nome or nome in vistos:
            continue
        vistos.add(nome)
        unidades.append(
            {
                "id": str(bruto.get("id") or "").strip(),
                "nome": nome,
                "modalidade": str(bruto.get("modalidade") or "").strip(),
                "cod_coligada": str(bruto.get("cod_coligada") or "").strip(),
            }
        )

    unidades.sort(key=lambda item: item["nome"].lower())

    _cache.update(
        {
            "unidades_ts": agora,
            "unidades": unidades,
            "unidades_erro": "",
        }
    )

    return {
        "success": True,
        "unidades": unidades,
        "total": len(unidades),
        "erro": "",
        "cache": False,
    }


def _extrair_nome_item(
    item: dict[str, Any],
    chaves: tuple[str, ...],
) -> str:
    for chave in chaves:
        valor = _normalizar_nome(item.get(chave))
        if valor:
            return valor
    return ""


def _id_numerico(valor: Any) -> int | None:
    try:
        numero = int(str(valor or "").strip())
    except (TypeError, ValueError):
        return None
    return numero if numero > 0 else None


def _serie_exibir_kanban(bruto: dict[str, Any]) -> bool:
    """Exibe Fundamental, Médio e Pré; oculta Infantil."""
    nivel = str(bruto.get("nivel_ensino") or "").strip()
    if nivel == "1":
        return False
    if nivel in {"2", "3", "4"}:
        return True

    nome = _extrair_nome_item(bruto, ("nome", "serie", "nm_serie")).lower()
    if "infantil" in nome or nome.startswith("berç"):
        return False
    return True


def _normalizar_series(brutos: list[dict[str, Any]]) -> list[dict[str, str]]:
    series: list[dict[str, str]] = []
    vistos: set[str] = set()

    for bruto in brutos:
        if not isinstance(bruto, dict):
            continue
        if not _item_ativo(bruto):
            continue
        if not _serie_exibir_kanban(bruto):
            continue
        item_id = str(bruto.get("id") or bruto.get("id_serie") or "").strip()
        nome = _extrair_nome_item(bruto, ("nome", "serie", "nm_serie", "descricao", "name"))
        if not nome:
            continue
        chave = item_id or nome.casefold()
        if chave in vistos:
            continue
        vistos.add(chave)
        series.append(
            {
                "id": item_id,
                "nome": nome,
                "slug": str(bruto.get("slug") or "").strip(),
            }
        )

    series.sort(key=lambda item: item["nome"].lower())
    return series


def _normalizar_turmas(brutos: list[dict[str, Any]]) -> list[dict[str, str]]:
    turmas: list[dict[str, str]] = []
    vistos: set[str] = set()

    for bruto in brutos:
        if not isinstance(bruto, dict):
            continue
        if not _item_ativo(bruto):
            continue
        item_id = str(bruto.get("id") or bruto.get("id_turma") or "").strip()
        nome = _extrair_nome_item(bruto, ("nome", "turma", "nm_turma", "descricao", "name"))
        nome_exibicao = _extrair_nome_item(bruto, ("nome_exibicao",)) or nome
        if not nome_exibicao:
            continue
        chave = item_id or nome_exibicao.casefold()
        if chave in vistos:
            continue
        vistos.add(chave)
        turmas.append(
            {
                "id": item_id,
                "nome": nome or nome_exibicao,
                "nome_exibicao": nome_exibicao,
            }
        )

    turmas.sort(key=lambda item: item["nome_exibicao"].lower())
    return turmas


def _chave_cache_dependencia(*partes: str) -> str:
    return "|".join(str(parte or "").strip().lower() for parte in partes if str(parte or "").strip())


def listar_series_por_unidade(
    *,
    unidade_id: str = "",
    force: bool = False,
) -> dict[str, Any]:
    if not KEEPEDU_LISTAR_SERIES_URL:
        return {
            "success": False,
            "series": [],
            "total": 0,
            "erro": "Configure KEEPEDU_API_BASE_URL no .env.",
            "cache": False,
        }

    id_unidade = _id_numerico(unidade_id)
    if not id_unidade:
        return {
            "success": False,
            "series": [],
            "total": 0,
            "erro": "Informe a unidade para listar as séries.",
            "cache": False,
        }

    chave = _chave_cache_dependencia(str(id_unidade))
    agora = time.time()
    cache_series = _cache.setdefault("series", {})
    entrada_cache = cache_series.get(chave)

    if (
        not force
        and isinstance(entrada_cache, dict)
        and entrada_cache.get("itens")
        and (agora - float(entrada_cache.get("ts") or 0)) < _CACHE_TTL_SECONDS
    ):
        return {
            "success": True,
            "series": list(entrada_cache["itens"]),
            "total": len(entrada_cache["itens"]),
            "unidade": entrada_cache.get("unidade"),
            "erro": "",
            "cache": True,
        }

    corpo, erro = _post_listagem(
        KEEPEDU_LISTAR_SERIES_URL,
        "séries",
        {"unidade_id": id_unidade},
    )
    if erro or not corpo:
        return {
            "success": False,
            "series": [],
            "total": 0,
            "erro": erro,
            "cache": False,
        }

    brutos = _extrair_lista(corpo, ("series", "data", "items"))
    series = _normalizar_series(brutos)
    unidade = corpo.get("unidade") if isinstance(corpo.get("unidade"), dict) else None
    cache_series[chave] = {"ts": agora, "itens": series, "unidade": unidade}

    return {
        "success": True,
        "series": series,
        "total": len(series),
        "unidade": unidade,
        "erro": "",
        "cache": False,
    }


def listar_turmas_por_serie(
    *,
    unidade_id: str = "",
    serie_id: str = "",
    force: bool = False,
) -> dict[str, Any]:
    if not KEEPEDU_LISTAR_TURMAS_URL:
        return {
            "success": False,
            "turmas": [],
            "total": 0,
            "erro": "Configure KEEPEDU_API_BASE_URL no .env.",
            "cache": False,
        }

    id_unidade = _id_numerico(unidade_id)
    id_serie = _id_numerico(serie_id)
    if not id_unidade:
        return {
            "success": False,
            "turmas": [],
            "total": 0,
            "erro": "Informe a unidade para listar as turmas.",
            "cache": False,
        }
    if not id_serie:
        return {
            "success": False,
            "turmas": [],
            "total": 0,
            "erro": "Informe a série para listar as turmas.",
            "cache": False,
        }

    chave = _chave_cache_dependencia(str(id_unidade), str(id_serie))
    agora = time.time()
    cache_turmas = _cache.setdefault("turmas", {})
    entrada_cache = cache_turmas.get(chave)

    if (
        not force
        and isinstance(entrada_cache, dict)
        and entrada_cache.get("itens")
        and (agora - float(entrada_cache.get("ts") or 0)) < _CACHE_TTL_SECONDS
    ):
        return {
            "success": True,
            "turmas": list(entrada_cache["itens"]),
            "total": len(entrada_cache["itens"]),
            "unidade": entrada_cache.get("unidade"),
            "serie": entrada_cache.get("serie"),
            "erro": "",
            "cache": True,
        }

    corpo, erro = _post_listagem(
        KEEPEDU_LISTAR_TURMAS_URL,
        "turmas",
        {"unidade_id": id_unidade, "serie_id": id_serie},
    )
    if erro or not corpo:
        return {
            "success": False,
            "turmas": [],
            "total": 0,
            "erro": erro,
            "cache": False,
        }

    brutos = _extrair_lista(corpo, ("turmas", "classes", "data", "items"))
    turmas = _normalizar_turmas(brutos)
    unidade = corpo.get("unidade") if isinstance(corpo.get("unidade"), dict) else None
    serie = corpo.get("serie") if isinstance(corpo.get("serie"), dict) else None
    cache_turmas[chave] = {
        "ts": agora,
        "itens": turmas,
        "unidade": unidade,
        "serie": serie,
    }

    return {
        "success": True,
        "turmas": turmas,
        "total": len(turmas),
        "unidade": unidade,
        "serie": serie,
        "erro": "",
        "cache": False,
    }
