"""Compactacao transparente dos dados OMR persistidos por cartao."""
from __future__ import annotations

import base64
from copy import deepcopy
import json
import zlib
from typing import Any


CAMPO_PONTOS_COMPACTADOS = "pontos_mapeados_zlib_v1"
_LIMITE_PONTOS_DESCOMPACTADOS = 20 * 1024 * 1024
_CAMPOS_DIAGNOSTICO_TRANSITORIO = (
    "confianca_questoes",
    "pendencias_confianca",
    "total_pendencias_confianca",
)


def _arredondar_coordenada(valor: Any) -> Any:
    """Coordenadas subpixel nao alteram clique/desenho; pixels inteiros bastam."""
    try:
        return round(float(valor))
    except (TypeError, ValueError):
        return valor


def _normalizar_pontos_para_persistencia(pontos: dict) -> dict:
    resultado: dict[str, Any] = {}
    for pergunta, alternativas in pontos.items():
        if not isinstance(alternativas, dict):
            resultado[str(pergunta)] = deepcopy(alternativas)
            continue

        alternativas_compactas: dict[str, Any] = {}
        for alternativa, ponto in alternativas.items():
            if not isinstance(ponto, dict):
                alternativas_compactas[str(alternativa)] = deepcopy(ponto)
                continue
            ponto_compacto = deepcopy(ponto)
            if "x" in ponto_compacto:
                ponto_compacto["x"] = _arredondar_coordenada(ponto_compacto["x"])
            if "y" in ponto_compacto:
                ponto_compacto["y"] = _arredondar_coordenada(ponto_compacto["y"])
            alternativas_compactas[str(alternativa)] = ponto_compacto
        resultado[str(pergunta)] = alternativas_compactas
    return resultado


def _comprimir_json(valor: dict) -> str:
    conteudo = json.dumps(
        valor,
        ensure_ascii=False,
        separators=(",", ":"),
    ).encode("utf-8")
    return base64.b64encode(zlib.compress(conteudo, level=6)).decode("ascii")


def _descomprimir_json(conteudo: str) -> dict:
    comprimido = base64.b64decode(conteudo.encode("ascii"), validate=True)
    descompressor = zlib.decompressobj()
    bruto = descompressor.decompress(
        comprimido,
        _LIMITE_PONTOS_DESCOMPACTADOS + 1,
    )
    if (
        len(bruto) > _LIMITE_PONTOS_DESCOMPACTADOS
        or descompressor.unconsumed_tail
    ):
        raise ValueError("Mapa de pontos excede o limite permitido.")
    bruto += descompressor.flush()
    dados = json.loads(bruto.decode("utf-8"))
    if not isinstance(dados, dict):
        raise ValueError("Mapa de pontos compactado invalido.")
    return dados


def compactar_dados_leitura(dados: dict | None) -> dict:
    """Compacta somente o mapa volumoso, mantendo os demais campos consultaveis."""
    if not isinstance(dados, dict):
        return {}

    compacto = deepcopy(dados)
    pontos = compacto.get("pontos_mapeados")
    if isinstance(pontos, dict) and pontos:
        pontos_normalizados = _normalizar_pontos_para_persistencia(pontos)
        compacto[CAMPO_PONTOS_COMPACTADOS] = _comprimir_json(pontos_normalizados)
        compacto.pop("pontos_mapeados", None)

    for campo in _CAMPOS_DIAGNOSTICO_TRANSITORIO:
        compacto.pop(campo, None)
    if not compacto.get("debug_bolhas"):
        compacto.pop("debug_bolhas", None)
    return compacto


def hidratar_dados_leitura(dados: dict | None) -> dict:
    """Restaura o contrato historico consumido pelo restante da aplicacao."""
    if not isinstance(dados, dict):
        return {}

    hidratado = deepcopy(dados)
    conteudo = hidratado.pop(CAMPO_PONTOS_COMPACTADOS, None)
    if "pontos_mapeados" not in hidratado and isinstance(conteudo, str) and conteudo:
        try:
            hidratado["pontos_mapeados"] = _descomprimir_json(conteudo)
        except (ValueError, TypeError, UnicodeError, json.JSONDecodeError, zlib.error):
            # Uma linha corrompida nao deve impedir a abertura das demais.
            hidratado["pontos_mapeados"] = {}
    return hidratado
