"""
Semantica de las salidas digitales del generador (FSM NANOOX).

El controlador publica `digital_outputs_state` como un string de bits, y
`control_generator_back` como el modo de control por falla:

- False -> la misma orden de marcha va a todas las salidas configuradas
  ("00"/"11" con dos salidas, "000"/"111" con tres).
- True  -> Q1 (linea principal) y Q3 (respaldo) se reparten la orden, asi que
  aparecen patrones mixtos ("110", "011", "101", ...).
- Ausente -> se resuelve como False: el firmware antiguo no publica el campo y
  opera en modo todo-o-nada.

Reglas. Unica fuente de verdad, replicada en
SW_SERVICE_MANAGER_FE/src/services/Nanoox/utils/generatorOutputs.js:

    encendido     = algun bit en 1                    (independiente del modo)
    inconsistente = modo False EXPLICITO y bits mezclados

La inconsistencia NO se deriva del patron de bits en modo enclavado ni en modo
desconocido: la nube no puede enumerar de forma confiable los patrones validos
(p.ej. "101"), y declararlo produce alarmas criticas falsas.

OJO con GEN_FAULT: en los datos reales GEN_FAULT/GEN_FAULT_LATCHED valen True
exactamente cuando el generador corre en la linea de respaldo ("011", con
output_fault_state activo) y False cuando corre en la principal ("110"). Es la
senal que dispara el traspaso a respaldo, NO una alarma del generador. Tratarla
como alarma pinta en rojo justo la operacion normal que este contrato soporta.
"""

ALL_OR_NOTHING = "todo_o_nada"
INTERLOCKED = "enclavado"

_TEKFISH_MARKER = "falla_generador_tekfish"


def as_bool(value):
    """Normaliza a True/False/None. None significa "el dato no permite decidir"."""
    if isinstance(value, bool):
        return value
    if isinstance(value, int) and value in (0, 1):
        return bool(value)
    if isinstance(value, str):
        text = value.strip().lower()
        if text in ("true", "1"):
            return True
        if text in ("false", "0"):
            return False
    return None


def normalize_outputs(digital_outputs_state):
    """Devuelve el string de salidas si es utilizable, o None."""
    if not isinstance(digital_outputs_state, str):
        return None
    value = digital_outputs_state.strip()
    if not value or any(char not in "01" for char in value):
        return None
    return value


def resolve_control_mode(control_generator_back):
    """Devuelve (modo, reportado). Ausente -> (ALL_OR_NOTHING, False)."""
    flag = as_bool(control_generator_back)
    if flag is True:
        return INTERLOCKED, True
    return ALL_OR_NOTHING, flag is False


def is_tekfish(snapshot):
    """Los tekfish codifican `digital_outputs_state` de otra forma."""
    return isinstance(snapshot, dict) and _TEKFISH_MARKER in snapshot


def outputs_are_on(digital_outputs_state):
    """Encendido = algun bit en 1. None si no hay dato utilizable."""
    outputs = normalize_outputs(digital_outputs_state)
    if outputs is None:
        return None
    return "1" in outputs


def outputs_are_inconsistent(digital_outputs_state, control_generator_back):
    """
    Solo se declara inconsistencia con control_generator_back = False explicito,
    donde el contrato garantiza que todas las salidas reciben la misma orden.
    """
    if as_bool(control_generator_back) is not False:
        return False
    outputs = normalize_outputs(digital_outputs_state)
    if outputs is None or len(outputs) < 2:
        return False
    return len(set(outputs)) > 1


def generator_is_on(snapshot):
    """
    Encendido/apagado desde el snapshot completo. None si no se puede decidir.
    """
    if not isinstance(snapshot, dict):
        return None

    outputs = normalize_outputs(snapshot.get("digital_outputs_state"))

    if is_tekfish(snapshot):
        # "10" = senal de encendido enviada, "01" = senal de apagado enviada.
        if outputs == "10":
            return True
        if outputs == "01":
            return False
        return None

    return outputs_are_on(outputs)


def resolve_run_order(snapshot):
    """Orden de marcha segun la FSM (MODE/ON o REMOTE_ON). None si no la reporta."""
    if not isinstance(snapshot, dict):
        return None
    if snapshot.get("MODE"):
        return as_bool(snapshot.get("ON"))
    return as_bool(snapshot.get("REMOTE_ON"))
