"""Control y cola en memoria. Sin red, GPIO ni dependencias externas."""
from copy import deepcopy
from math import isfinite


def numero(valor):
    return type(valor) in (int, float) and isfinite(valor)


def validar_config(config):
    if not isinstance(config, dict) or set(config) != {"version", "on", "off"}:
        raise ValueError("Se requieren version, on y off")
    if type(config["version"]) is not int or not 1 <= config["version"] <= 1000000:
        raise ValueError("Versión entera entre 1 y 1000000")
    if not all(numero(config[k]) for k in ("on", "off")):
        raise ValueError("Umbrales numéricos finitos")
    if not -40 <= config["off"] < config["on"] <= 125:
        raise ValueError("Se exige -40 <= off < on <= 125")
    return deepcopy(config)


class Dispositivo:
    def __init__(self, boot="demo"):
        self.boot = boot
        self.ahora = 0
        self.config = dict(version=1, on=28, off=26)
        self.valor = None
        self.medida_en = None
        self.salida = False
        self.seq = 0
        self.cola = []
        self.descartes = 0

    def controlar(self):
        if (self.valor is None or self.medida_en is None
                or self.ahora - self.medida_en > 5):
            self.salida = False
        elif self.valor >= self.config["on"]:
            self.salida = True
        elif self.valor <= self.config["off"]:
            self.salida = False

    def avanzar(self, segundos):
        if type(segundos) is not int or not 1 <= segundos <= 3600:
            raise ValueError("Avance entero entre 1 y 3600 segundos")
        self.ahora += segundos
        self.controlar()

    def medir(self, valor):
        if valor is not None and (not numero(valor) or not -40 <= valor <= 125):
            raise ValueError("Lectura fuera de rango")
        self.avanzar(1)
        self.valor = valor
        self.medida_en = self.ahora
        self.controlar()
        self.seq += 1
        registro = dict(schema=1, device="medidor-01", boot=self.boot,
                        seq=self.seq, tick=self.ahora, temperature=valor,
                        quality="invalid" if valor is None else "valid",
                        output=self.salida, config_version=self.config["version"])
        if len(self.cola) < 3:
            self.cola.append(registro)
            return True
        self.descartes += 1  # Descartar nueva historia; el control ya se atendió.
        return False

    def configurar(self, propuesta):
        config = validar_config(propuesta)
        if config["version"] < self.config["version"]:
            raise ValueError("Versión anterior")
        if config["version"] == self.config["version"]:
            if config != self.config:
                raise ValueError("Misma versión con contenido diferente")
            return False  # Repetición exacta: no vuelve a aplicar.
        self.config = config
        self.controlar()
        return True

    def confirmar(self, boot, seq):
        if self.cola and (self.cola[0]["boot"], self.cola[0]["seq"]) == (boot, seq):
            self.cola.pop(0)
            return True
        return False
