import json
import os
import threading
import time
from datetime import datetime, timedelta
import csv
import sqlite3

from flask import Flask, jsonify, send_from_directory, send_file, request

import paho.mqtt.client as mqtt


TEAMS_SEND_PASSWORD = os.environ.get("TEAMS_SEND_PASSWORD", "admin123")
TEAMS_ALLOWED_GROUPS = {"G1", "G2", "G3"}
BYPASS_PASSWORD = os.environ.get("BYPASS_PASSWORD", "admin123")

# =====================================================
# CONFIG MQTT (TEM DE BATER CERTO COM O PUBLICADOR)
# =====================================================
MQTT_BROKER = os.environ.get("MQTT_BROKER", "100.87.44.88")
MQTT_PORT = int(os.environ.get("MQTT_PORT", "1883"))
MQTT_USER = os.environ.get("MQTT_USER", "client1")
MQTT_PASS = os.environ.get("MQTT_PASS", "client1")
MQTT_BASE_TOPIC = os.environ.get("MQTT_BASE_TOPIC", "fabrica/maquinas")

# =====================================================
# CONFIGURACAO DAS 3 CENTRAIS
# =====================================================
CENTRAIS_CONFIG = {
    "SEA": {
        "nome": "SEA",
        "descricao": "Central SEA - Sistema de Filtragem",
        "topicos": {
            "f1": "Serratec/SEA_F1",
            "f2": "Serratec/SEA_F2",
            "f3": "Serratec/SEA_F3",
            "f4": "Serratec/SEA_F4",
            "dirty": "Serratec/SEA_L",
            "clean": "Serratec/SEA_PROPRE",
            "treatment": "Serratec/SEA_POT_L",
            "treatment_state": "Serratec/SEA_POT",
            "fgr": "Serratec/SEA_FGRstate",
            "sensores": "Serratec/SEA_SENSOR/#"
        }
    },
    "SEG": {
        "nome": "SEG",
        "descricao": "Central SEG - Sistema de Engenharia",
        "topicos": {
            "f1": "Serratec/SEG_F1",
            "f2": "Serratec/SEG_F2",
            "f3": "Serratec/SEG_F3",
            "f4": "Serratec/SEG_F4",
            "dirty": "Serratec/SEG_L",
            "clean": "Serratec/SEG_PROPRE",
            "treatment": "Serratec/SEG_POT_L",
            "treatment_state": "Serratec/SEG_POT",
            "fgr": "Serratec/SEG_FGRstate",
            "sensores": "Serratec/SEG_SENSOR/#"
        }
    },
    "SES": {
        "nome": "SES",
        "descricao": "Central SES - Sistema de Secagem",
        "topicos": {
            "f1": "Serratec/SES_F1",
            "f2": "Serratec/SES_F2",
            "f3": "Serratec/SES_F3",
            "f4": "Serratec/SES_F4",
            "dirty": "Serratec/SES_L",
            "clean": "Serratec/SES_PROPRE",
            "treatment": "Serratec/SES_POT_L",
            "treatment_state": "Serratec/SES_POT",
            "fgr": "Serratec/SES_FGRstate",
            "sensores": "Serratec/SES_SENSOR/#"
        }
    }
}
# Tópicos MQTT para controlo do bypass
BYPASS_TOPICS = {
    "mode": "Serratec/bypass/mode",        # bool: true=manual, false=auto
    "troca": "Serratec/bypass/troca",      # bool: true=trocar
    "setpoint": "Serratec/bypass/setpoint" # número: valor setpoint
}

# Estado do bypass (para ler via API)
bypass_state = {
    "mode": False,        # False=auto, True=manual
    "central": "SEA",     # Central atual em bypass
    "setpoint": 500,      # Setpoint atual
    "ativo": False        # Se bypass está realmente ativo
}

# =====================================================
# ESTADOS (MESMA TABELA DO PUBLICADOR, PARA RESUMO)
# =====================================================
ESTADOS = {
    1: "Em Produção",
    2: "Máquina Parada",
    4: "Pronta a Produzir",
    8: "Em Carregamento",
    16: "Em Aquecimento",
    0: "Desconhecido",
}

# =====================================================
# CONFIGURACAO DOS SENSORES (igual ao teu original)
# =====================================================
SENSOR_CONFIG = [
    {"name": "Disjunção variador BP", "topic": "Serratec/SENSOR_0"},
    {"name": "Disjunção Bomba secundaria arr", "topic": "Serratec/SENSOR_1"},
    {"name": "Disjunção Motor fgr", "topic": "Serratec/SENSOR_2"},
    {"name": "Disjunção bomba de lavagem fgr", "topic": "Serratec/SENSOR_3"},
    {"name": "Defeito variador circulação", "topic": "Serratec/SENSOR_4"},
    {"name": "Defeito variador HP", "topic": "Serratec/SENSOR_5"},
    {"name": "Defeito variador drague", "topic": "Serratec/SENSOR_6"},
    {"name": "Defeito variador Arrosage", "topic": "Serratec/SENSOR_7"},
    {"name": "Disjunção bomba filtro 1", "topic": "Serratec/SENSOR_8"},
    {"name": "Disjunção bomba filtro 2", "topic": "Serratec/SENSOR_9"},
    {"name": "Disjunção bomba filtro 3", "topic": "Serratec/SENSOR_10"},
    {"name": "Disjunção bomba filtro 4", "topic": "Serratec/SENSOR_11"},
    {"name": "Disjunção agitador enduisage", "topic": "Serratec/SENSOR_12"},
    {"name": "Disjunção doseador farinha", "topic": "Serratec/SENSOR_13"},
    {"name": "Disjunção aspirador farinha", "topic": "Serratec/SENSOR_14"},
    {"name": "Disjunção agitador tratamento", "topic": "Serratec/SENSOR_15"},
    {"name": "Disjunção bomba tratamento", "topic": "Serratec/SENSOR_16"},
    {"name": "Disjunção bomba circulação", "topic": "Serratec/SENSOR_17"},
    {"name": "Disjunção purificador de ar", "topic": "Serratec/SENSOR_18"},
    {"name": "Disjunção agitador reservatório sujo", "topic": "Serratec/SENSOR_19"},
    {"name": "Disjunção variador HP", "topic": "Serratec/SENSOR_20"},
    {"name": "Defeito de tensão", "topic": "Serratec/SENSOR_21"},
    {"name": "Defeito de ar no ilot Pot das Borras", "topic": "Serratec/SENSOR_22"},
    {"name": "Nível alto de tratamento", "topic": "Serratec/SENSOR_23"},
    {"name": "Nível baixo de tratamento", "topic": "Serratec/SENSOR_24"},
    {"name": "Defeito de ar sopro POT 1", "topic": "Serratec/SENSOR_25"},
    {"name": "Defeito contentor de borras ausente", "topic": "Serratec/SENSOR_26"},
    {"name": "Contentor de borras cheio", "topic": "Serratec/SENSOR_27"},
    {"name": "Defeito debito bomba de tratamento", "topic": "Serratec/SENSOR_28"},
    {"name": "Defeito bloqueio do pot das borras", "topic": "Serratec/SENSOR_29"},
    {"name": "Defeito de fecho do pot das borras", "topic": "Serratec/SENSOR_30"},
    {"name": "Defeito de abertura do pot das borras", "topic": "Serratec/SENSOR_31"},
    {"name": "Defeito tempo de sopragem A 0", "topic": "Serratec/SENSOR_32"},
    {"name": "Defeito de bloqueio da tampa POT", "topic": "Serratec/SENSOR_33"},
    {"name": "Defeito Porta de acesso aberta", "topic": "Serratec/SENSOR_34"},
    {"name": "Defeito falta de farinha", "topic": "Serratec/SENSOR_35"},
]

# =====================================================
# DATA STORES POR CENTRAL
# =====================================================
def criar_data_store():
    return {
        "filters": {"f1": 0, "f2": 0, "f3": 0, "f4": 0},
        "tanks": {"dirty": 0, "clean": 0, "treatment": 0},
        "treatment_state": "Parado",
        "fgr": False,
        "mqtt_status": "Desligado",
        "sensors": [
            {
                "name": s["name"],
                "topic": s["topic"],
                "value": False,
                "alarm": False
            }
            for s in SENSOR_CONFIG
        ]
    }

data_stores = {
    "SEA": criar_data_store(),
    "SEG": criar_data_store(),
    "SES": criar_data_store()
}
# =====================================================
# DATA AUXILIAR PARA O NOVO DASHBOARD
# =====================================================
data_aux = {
    "oils": {"SEA": 0.0, "SEG": 0.0, "SES": 0.0},
    "bypass": "",
    "setpoint": 0.0,
    "sfe": {
        "temp_out": 0.0,
        "temp_in": 0.0,
        "press": 0.0,
        "delta": 0.0,
        "kwh": 0.0,
        "valor43": 0.0,
        "valor44": 0.0,
        "valor45": 0.0,
        "valor46": 0.0,
        "valor48": 0.0,
        "valor49": 0.0,
        "valor50": 0.0,
        "valor51": 0.0,
        "valor52":False,
        "valor53":False,
        "valor54":False,
        "valor55":False,
        "valor56":False,
        "valor57":False,
        "valor58":False,
        "valor59":False,
        "valor60":False,
        "valor61":False,
        "valor3":False,
        "valor62":False,
        "valor63":False,
        "valor64":False,
        "valor65":False,
        "valor66":False,
        "valor67":False,
        "valor68":False,
        "valor69":False,
        "valor70":False,
        "valor71":False,
        "valor72":False,
        "valor73":False,
        "valor74":False,
        "valor75":False,
        "valor76":False,
        "valor77":False,
    },
    "sff": {
        "temp_out": 0.0,
        "temp_in": 0.0,
        "press": 0.0,
        "delta": 0.0,
        "kwh": 0.0,
        "valor8": 0.0,
        "valor9": 0.0,
        "valor10": 0.0,
        "valor11": 0.0,
        "valor13": 0.0,
        "valor14": 0.0,
        "valor15": 0.0,
        "valor16": 0.0,
        "valor17":False,
        "valor18":False,
        "valor19":False,
        "valor20":False,
        "valor21":False,
        "valor22":False,
        "valor23":False,
        "valor24":False,
        "valor25":False,
        "valor26":False,
        "valor2":False,
        "valor27":False,
        "valor28":False,
        "valor29":False,
        "valor30":False,
        "valor31":False,
        "valor32":False,
        "valor33":False,
        "valor34":False,
        "valor35":False,
        "valor36":False,
        "valor37":False,
        "valor38":False,
        "valor39":False,
        "valor40":False,
        "valor41":False,
        "valor42":False,
    },
    "sfg" : {
        "agua_tempsfg":0.0,
        "press_agua": 0.0,
        "press_ar": 0.0,
        "temp_tanque1": 0.0,
        "temp_tanque2": 0.0,
        "temp_cam": 0.0,
        "temp_des1": 0.0,
        "temp_des2": 0.0,
        "prog_lav": 0,
        "count": 0,
        "lav_cesto": 0,
        "nivel_tanquepass": 0,
        "temp_tanquepass": 0,
        "countpass": 0,
        "prog_pass": 0,
        "lav_cestopass": 0,
        "temp_campass":0,
        "filtro": 0,
        "filtro_rest":0,
        "estado":"",
        "estado_lub":"",
        "manutencao":False,
        "press_cam":0,
        "press_dest1":0,
        "press_dest2":0,
        "press_tanques":0,
        "press_ext":0,
        "lavauto":False,
        "lavman":False,
        "lubauto":False,
        "lubman":False,
        "tapeteman":False,
        "tapetauto":False,
        "porta_lav_aberta":False,
        "porta_lav_fechada":False,
        "porta_lub_aberta":False,
        "porta_lub_fechada":False,
        "func_dir":False,
        "filtrolimalhas": 0,
        "lav_camaranivel":"",
        "lub_camaranivel":"",
        "lub_tanquepress": 0,
        "still1_min": False,
        "still1_max": False,
        "still2_min": False,
        "still2_max": False,
        "tanque1_min": False,
        "tanque1_max": False,
        "tanque2_min": False,
        "tanque2_max": False,
        "tanquelub_min":False,
        "tanquelub_max":False,
    },
    "seu" : {
        "agua_temp":0.0,
        "press_agua": 0.0,
        "press_ar": 0.0,
        "temp_tanque1": 0.0,
        "temp_tanque2": 0.0,
        "temp_cam": 0.0,
        "temp_des1": 0.0,
        "temp_des2": 0.0,
        "prog_lav": 0,
        "count": 0,
        "lav_cesto": 0,
        "nivel_tanquepass": 0,
        "temp_tanquepass": 0,
        "countpass": 0,
        "prog_pass": 0,
        "lav_cestopass": 0,
        "temp_campass":0,
        "filtro": 0,
        "filtro_rest":0,
        "estado":"",
        "estado_lub":"",
        "manutencao":False,
        "press_cam":0,
        "press_dest1":0,
        "press_dest2":0,
        "press_tanques":0,
        "press_ext":0,
        "lavauto":False,
        "lavman":False,
        "lubauto":False,
        "lubman":False,
        "tapeteman":False,
        "tapetauto":False,
        "porta_lav_aberta":False,
        "porta_lav_fechada":False,
        "porta_lub_aberta":False,
        "porta_lub_fechada":False,
        "func_dir":False,
        "filtrolimalhas": 0,
        "lav_camaranivel":"",
        "lub_camaranivel":"",
        "lub_tanquepress": 0,
        "still1_min": False,
        "still1_max": False,
        "still2_min": False,
        "still2_max": False,
        "tanque1_min": False,
        "tanque1_max": False,
        "tanque2_min": False,
        "tanque2_max": False,
        "tanquelub_min":False,
        "tanquelub_max":False,
    },




    
    "geral": {
        "amb_temp": 0.0,
        "agua_temp": 0.0,
        "extra": {}           # valores dinâmicos: EXTRA_*
    },
    "maquinas": {
        "SFG": 0, "SFGp": 0, "SEU": 0, "SEUp": 0, "SEH1": 0, "SEH2": 0, "SEB": 0
        # 0 = stop | 1 = ok | 2 = idle
    },
    "energia": {
        "total": 0.0,
        "now": 0.0
    }
}

# =====================================================
# ESTRUTURA DE DADOS EM MEMORIA (MANTIDA IGUAL)
# =====================================================
dados_maquinas = {}
lock = threading.Lock()

SCRIPT_START_TS = time.time()
SCRIPT_START_ISO = datetime.now().isoformat()
RESET_TEMPOS_COUNT = 0
ULTIMO_RESET_TEMPOS_ISO = None

def reset_script_start():
    global SCRIPT_START_TS, SCRIPT_START_ISO
    SCRIPT_START_TS = time.time()
    SCRIPT_START_ISO = datetime.now().isoformat()

def reset_tempos_acumulados():
    global RESET_TEMPOS_COUNT, ULTIMO_RESET_TEMPOS_ISO

    with lock:
        total_resetadas = 0
        for nome, maquina in dados_maquinas.items():
            if maquina.get("tipo") != "DUAL_M":
                maquina["tempos_estado"] = {}
                total_resetadas += 1
            else:
                sub = maquina.get("sub") or {}
                lav = sub.get("lavagem") or {}
                pas = sub.get("passivacao") or {}

                if lav.get("tempos_estado") is not None:
                    lav["tempos_estado"] = {}
                    total_resetadas += 1
                if pas.get("tempos_estado") is not None:
                    pas["tempos_estado"] = {}
                    total_resetadas += 1

        RESET_TEMPOS_COUNT += 1
        ULTIMO_RESET_TEMPOS_ISO = datetime.now().isoformat()

        return {
            "reset_count": RESET_TEMPOS_COUNT,
            "resetadas": total_resetadas,
            "total_maquinas": len(dados_maquinas),
            "ultimo_reset_iso": ULTIMO_RESET_TEMPOS_ISO
        }

# =====================================================
# BANCO DE DADOS PARA HISTORICO DAS 3 CENTRAIS
# =====================================================
def init_database():
    conn = sqlite3.connect('centrais.db')
    c = conn.cursor()
    
    c.execute('''CREATE TABLE IF NOT EXISTS centrais
                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
                  nome TEXT UNIQUE,
                  descricao TEXT)''')
    
    c.execute('''CREATE TABLE IF NOT EXISTS historico
                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
                  central_id INTEGER,
                  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                  f1 REAL, f2 REAL, f3 REAL, f4 REAL,
                  dirty REAL, clean REAL, treatment REAL,
                  treatment_state TEXT, fgr INTEGER)''')
    
    c.execute('''CREATE TABLE IF NOT EXISTS alarmes
                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
                  central_id INTEGER,
                  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                  tipo TEXT, mensagem TEXT, severidade TEXT)''')
    
    for nome in CENTRAIS_CONFIG.keys():
        c.execute("INSERT OR IGNORE INTO centrais (nome, descricao) VALUES (?, ?)",
                 (nome, f"Central {nome}"))
    
    conn.commit()
    conn.close()
    print("Banco de dados inicializado")

init_database()

def get_central_id(nome_central):
    conn = sqlite3.connect('centrais.db')
    c = conn.cursor()
    c.execute("SELECT id FROM centrais WHERE nome = ?", (nome_central,))
    result = c.fetchone()
    conn.close()
    return result[0] if result else None

def save_to_database(nome_central, data):
    central_id = get_central_id(nome_central)
    if not central_id:
        return
    
    try:
        conn = sqlite3.connect('centrais.db')
        c = conn.cursor()
        c.execute("""INSERT INTO historico 
                     (central_id, f1, f2, f3, f4, dirty, clean, treatment, treatment_state, fgr)
                     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                  (central_id,
                   data["filters"]["f1"], data["filters"]["f2"],
                   data["filters"]["f3"], data["filters"]["f4"],
                   data["tanks"]["dirty"], data["tanks"]["clean"],
                   data["tanks"]["treatment"], data["treatment_state"],
                   1 if data["fgr"] else 0))
        conn.commit()
        conn.close()
    except Exception as e:
        print(f"Erro ao salvar {nome_central}: {e}")

def save_alarm_to_database(nome_central, message, severity):
    central_id = get_central_id(nome_central)
    if not central_id:
        return
    
    try:
        conn = sqlite3.connect('centrais.db')
        c = conn.cursor()
        c.execute("INSERT INTO alarmes (central_id, tipo, mensagem, severidade) VALUES (?, ?, ?, ?)",
                  (central_id, "sensor", message, severity))
        conn.commit()
        conn.close()
    except Exception as e:
        print(f"Erro ao salvar alarme: {e}")

# =====================================================
# MQTT CLIENTE 
# =====================================================
mqtt_client = None

def on_connect(client, userdata, flags, rc, properties=None):
    print("MQTT conectado, code:", rc)
    
    if rc == 0:
        # Atualiza status de todas as centrais
        for central in data_stores.values():
            central["mqtt_status"] = "Ligado"
        
        print("MQTT conectado com sucesso ao broker!")
        
        # Inscreve nos topicos de todas as centrais
        for central_nome, config in CENTRAIS_CONFIG.items():
            for topico in config["topicos"].values():
                client.subscribe(topico)
                print(f"  Inscrito: {topico}")
        
        # Tambem mantem as subscrições originais
        client.subscribe(f"{MQTT_BASE_TOPIC}/#", qos=0)
        client.subscribe(f"{MQTT_BASE_TOPIC}/+/manutencao/cmd", qos=0)
        # -----------------------------
        # Tópicos do painel auxiliar
        # -----------------------------
        aux_topics = [
            "Serratec/SEA_OIL_DIRTY",
            "Serratec/SEG_OIL_DIRTY",
            "Serratec/SES_OIL_DIRTY",
            "Serratec/BYPASS",
            "Serratec/SETPOINT",
            "Serratec/SFE_TEMP_OUT", 
            "Serratec/SFE_PRESS", "Serratec/SFE_DELTAP",
            "Serratec/SFF_TEMP_OUT", 
            "Serratec/SFF_PRESS", "Serratec/SFF_DELTAP",
            "Serratec/AMBIENTE_TEMP", "Serratec/AGUA_TEMP",
            "Serratec/ENERGIA_TOTAL", "Serratec/ENERGIA_NOW",
            "Serratec/bypass/ativo",
            "Serratec/bypass/central",
            "Serratec/bypass/mode",
            "Serratec/bypass/setpoint",
            #### Topicos Chiller SFF
            "Serratec/CH/SFF/valor0", "Serratec/CH/SFF/valor2", "Serratec/CH/SFF/valor4", "Serratec/CH/SFF/valor5", "Serratec/CH/SFF/valor8", "Serratec/CH/SFF/valor9", "Serratec/CH/SFF/valor10", "Serratec/CH/SFF/valor11","Serratec/CH/SFF/valor12",
            "Serratec/CH/SFF/valor13", "Serratec/CH/SFF/valor14", "Serratec/CH/SFF/valor15", "Serratec/CH/SFF/valor16", "Serratec/CH/SFF/valor17", "Serratec/CH/SFF/valor18", "Serratec/CH/SFF/valor19", "Serratec/CH/SFF/valor20", "Serratec/CH/SFF/valor21",
            "Serratec/CH/SFF/valor22", "Serratec/CH/SFF/valor23", "Serratec/CH/SFF/valor24", "Serratec/CH/SFF/valor25", "Serratec/CH/SFF/valor26", "Serratec/CH/SFF/valor27", "Serratec/CH/SFF/valor28", "Serratec/CH/SFF/valor29", "Serratec/CH/SFF/valor30",
            "Serratec/CH/SFF/valor31", "Serratec/CH/SFF/valor32", "Serratec/CH/SFF/valor33", "Serratec/CH/SFF/valor34", "Serratec/CH/SFF/valor35", "Serratec/CH/SFF/valor36", "Serratec/CH/SFF/valor37", "Serratec/CH/SFF/valor38",
            "Serratec/CH/SFF/valor39", "Serratec/CH/SFF/valor40", "Serratec/CH/SFF/valor41", "Serratec/CH/SFF/valor42",
            "Serratec/CH/SFF/KWH",
            ### Topicos Chiller SFE 
            "Serratec/CH/SFE/valor1", "Serratec/CH/SFE/valor3", "Serratec/CH/SFE/valor6", "Serratec/CH/SFE/valor7", "Serratec/CH/SFE/valor43", "Serratec/CH/SFE/valor44", "Serratec/CH/SFE/valor45", "Serratec/CH/SFE/valor46","Serratec/CH/SFE/valor47",
            "Serratec/CH/SFE/valor48", "Serratec/CH/SFE/valor49", "Serratec/CH/SFE/valor50", "Serratec/CH/SFE/valor51", "Serratec/CH/SFE/valor52", "Serratec/CH/SFE/valor53", "Serratec/CH/SFE/valor54", "Serratec/CH/SFE/valor55", "Serratec/CH/SFE/valor56",
            "Serratec/CH/SFE/valor57", "Serratec/CH/SFE/valor58", "Serratec/CH/SFE/valor59", "Serratec/CH/SFE/valor60", "Serratec/CH/SFE/valor61", "Serratec/CH/SFE/valor62", "Serratec/CH/SFE/valor63", "Serratec/CH/SFE/valor64", "Serratec/CH/SFE/valor64",
            "Serratec/CH/SFE/valor65", "Serratec/CH/SFE/valor66", "Serratec/CH/SFE/valor67", "Serratec/CH/SFE/valor68", "Serratec/CH/SFE/valor69", "Serratec/CH/SFE/valor70", "Serratec/CH/SFE/valor71", "Serratec/CH/SFE/valor72",
            "Serratec/CH/SFE/valor73", "Serratec/CH/SFE/valor74", "Serratec/CH/SFE/valor75", "Serratec/CH/SFE/valor76", "Serratec/CH/SFE/valor77",
            "Serratec/CH/SFE/KWH",
            ### Topicos SFG
            "Serratec/SFG_temp_agua","Serratec/SFG_press_agua","Serratec/SFG_press_ar", "Serratec/SFG_temp_tanque1","Serratec/SFG_temp_tanque2","Serratec/SFG_temp_cam","Serratec/SFG_temp_des1", "Serratec/SFG_temp_des2","Serratec/SFG_prog_lav", "Serratec/SFG_count", "Serratec/SFG_lav_cesto",
            "Serratec/SFG_nivel_tanquePass","Serratec/SFG_temp_tanquePass","Serratec/SFG_temp_pretanquePass","Serratec/SFG_countpass","Serratec/SFG_prog_pass","Serratec/SFG_lav_cestopass","Serratec/SFG_temp_campass", "Serratec/SFG_filtro","Serratec/SFG_estado",
            "Serratec/SFG_press_cam", "Serratec/SFG_press_dest1", "Serratec/SFG_press_dest2", "Serratec/SFG_press_tanques", "Serratec/SFG_press_ext","Serratec/SFG_manutencao", "Serratec/SFG_filtro_rest", "Serratec/SFG_lavauto", "Serratec/SFG_lavman", "Serratec/SFG_lubman", "Serratec/SFG_lubauto", "Serratec/SFG_tapeteman",
            "Serratec/SFG_tapeteauto", "Serratec/SFG_porta_lav_aberta", "Serratec/SFG_porta_lav_fechada", "Serratec/SFG_porta_lub_aberta", "Serratec/SFG_porta_lub_fechada",  "Serratec/SFG_func_dir", "Serratec/SFG_estado_lub", "Serratec/SFG_filtrolimalhas", "Serratec/SFG_lav_camaranivel", "Serratec/SFG_lub_tanquepress", "Serratec/SFG_still1_min", "Serratec/SFG_still1_max", "Serratec/SFG_still2_min", "Serratec/SFG_still2_max", "Serratec/SFG_tanque1_min", "Serratec/SFG_tanque1_max", "Serratec/SFG_tanque2_min",
            "Serratec/SFG_tanque2_max","Serratec/SFG_tanquelub_min","Serratec/SFG_tanquelub_max",
            ### Topicos SEU
            "Serratec/SEU_temp_agua","Serratec/SEU_press_agua","Serratec/SEU_press_ar", "Serratec/SEU_temp_tanque1","Serratec/SEU_temp_tanque2","Serratec/SEU_temp_cam","Serratec/SEU_temp_des1", "Serratec/SEU_temp_des2","Serratec/SEU_prog_lav", "Serratec/SEU_count", "Serratec/SEU_lav_cesto",
            "Serratec/SEU_nivel_tanquePass","Serratec/SEU_temp_tanquePass","Serratec/SEU_temp_pretanquePass","Serratec/SEU_countpass","Serratec/SEU_prog_pass","Serratec/SEU_lav_cestopass","Serratec/SEU_temp_campass", "Serratec/SEU_filtro","Serratec/SEU_estado",
            "Serratec/SEU_press_cam", "Serratec/SEU_press_dest1", "Serratec/SEU_press_dest2", "Serratec/SEU_press_tanques", "Serratec/SEU_press_ext","Serratec/SEU_manutencao", "Serratec/SEU_filtro_rest", "Serratec/SEU_lavauto", "Serratec/SEU_lavman", "Serratec/SEU_lubman", "Serratec/SEU_lubauto", "Serratec/SEU_tapeteman",
            "Serratec/SEU_tapeteauto", "Serratec/SEU_porta_lav_aberta", "Serratec/SEU_porta_lav_fechada", "Serratec/SEU_porta_lub_aberta", "Serratec/SEU_porta_lub_fechada",  "Serratec/SEU_func_dir", "Serratec/SEU_estado_lub", "Serratec/SEU_filtrolimalhas", "Serratec/SEU_lav_camaranivel", "Serratec/SEU_lub_tanquepress", "Serratec/SEU_still1_min", "Serratec/SEU_still1_max", "Serratec/SEU_still2_min", "Serratec/SEU_still2_max", "Serratec/SEU_tanque1_min", "Serratec/SEU_tanque1_max", "Serratec/SEU_tanque2_min",
            "Serratec/SEU_tanque2_max","Serratec/SEU_tanquelub_min","Serratec/SEU_tanquelub_max",





        ]
        for t in aux_topics:
            client.subscribe(t)
            print(f"  Inscrito: {t}")

        # Wildcards (extras e estados de máquinas)
        client.subscribe("Serratec/EXTRA/#", qos=0)
        client.subscribe("Serratec/STATE/#", qos=0)
        print("  Inscrito: Serratec/EXTRA/#")
        print("  Inscrito: Serratec/STATE/#")
        print("Subscrições realizadas com sucesso")
    else:
        for central in data_stores.values():
            central["mqtt_status"] = "Desligado"
        print(f"Falha na conexao MQTT. Codigo: {rc}")

def on_disconnect(client, userdata, rc):
    for central in data_stores.values():
        central["mqtt_status"] = "Desligado"
    print("MQTT desconectado do broker")
    

def on_message(client, userdata, msg):
    topic = msg.topic
    payload = msg.payload.decode("utf-8")

    try:
        # =================================================
        # IDENTIFICAR QUAL CENTRAL
        # =================================================
        central_encontrada = None
        topico_sem_prefixo = topic
        
        for central_nome in CENTRAIS_CONFIG.keys():
            if topic.startswith(f"Serratec/{central_nome}_"):
                central_encontrada = central_nome
                topico_sem_prefixo = topic.replace(f"Serratec/{central_nome}_", "")
                break
        
        if not central_encontrada:
            central_encontrada = "SEA"  # fallback para compatibilidade
            topico_sem_prefixo = topic.replace("Serratec/", "")
        
        data_store = data_stores[central_encontrada]

        # =================================================
        # PROCESSAR TOPICOS (igual ao teu original)
        # =================================================
        if topico_sem_prefixo == "F1":
            data_store["filters"]["f1"] = float(payload)
            save_to_database(central_encontrada, data_store)

        elif topico_sem_prefixo == "F2":
            data_store["filters"]["f2"] = float(payload)

        elif topico_sem_prefixo == "F3":
            data_store["filters"]["f3"] = float(payload)

        elif topico_sem_prefixo == "F4":
            data_store["filters"]["f4"] = float(payload)

        elif topico_sem_prefixo == "L":
            data_store["tanks"]["dirty"] = float(payload)
            data_aux["oils"][central_encontrada] = float(payload)#TESTE
            
        elif topico_sem_prefixo == "PROPRE":
            data_store["tanks"]["clean"] = float(payload)

        elif topico_sem_prefixo == "POT_L":
            data_store["tanks"]["treatment"] = float(payload)

        elif topico_sem_prefixo == "POT":
            data_store["treatment_state"] = payload

        elif topico_sem_prefixo == "FGRstate":
            data_store["fgr"] = payload == "1"

        
        elif topico_sem_prefixo.startswith("SENSOR"):
            try:
                idx = None
                # Aceitar "SENSOR_7"
                if "_" in topico_sem_prefixo:
                    idx = int(topico_sem_prefixo.split("_", 1)[1])
                    # Aceitar "SENSOR/7"
                elif "/" in topico_sem_prefixo:
                    idx = int(topico_sem_prefixo.split("/", 1)[1])

                    # Guard clause: se índice inválido, não segue adiante
                if idx is None or not (0 <= idx < len(data_store["sensors"])):
                    print(f"[SENSOR] Índice inválido em tópico '{topic}' com payload '{payload}'")
                    return

        # ---------- Parse de booleano robusto ----------
                s = payload.strip().lower()
                if s in ("1", "true", "on", "yes"):
                    is_active = True
                elif s in ("0", "false", "off", "no"):
                    is_active = False
                else:
                        # fallback: tentar json simples
                    try:
                        j = json.loads(s)
                        if isinstance(j, bool):
                                is_active = j
                        elif isinstance(j, (int, float)):
                            is_active = bool(j)
                        else:
                            print(f"[SENSOR] Payload não reconhecido como booleano: '{payload}'")
                            return
                    except Exception:
                            print(f"[SENSOR] Payload não reconhecido como booleano: '{payload}'")
                            return

        # ---------- Estado anterior ----------
                prev_active = bool(data_store["sensors"][idx].get("alarm", False))

                # ---------- Atualiza memória ----------
                data_store["sensors"][idx]["value"] = is_active
                data_store["sensors"][idx]["alarm"] = is_active

                # ---------- Se houve transição, grava no histórico ----------
                if is_active != prev_active:
                    sensor_name = data_store["sensors"][idx]["name"]
                    if is_active:
                        save_alarm_to_database(
                            central_encontrada,
                            f" {sensor_name} ativo",
                            "danger"
                        )
                    else:
                        save_alarm_to_database(
                            central_encontrada,
                            f" {sensor_name} normalizado",
                            "success"
                        )

            except Exception as e:
                # Log detalhado e seguro (sem usar variáveis possivelmente não definidas)
                print(f"Erro sensor {central_encontrada} (topic='{topic}', payload='{payload}'): {e}")
        # ============================
        # TOPICOS AUXILIARES (NOVOS)
        # ============================

        # ÓLEO SUJO por central
        if topic == "Serratec/SEA_L":
            data_aux["oils"]["SEA"] = float(payload); return
        if topic == "Serratec/SEG_L":
            data_aux["oils"]["SEG"] = float(payload); return
        if topic == "Serratec/SES_L":
            data_aux["oils"]["SES"] = float(payload); return

        # BYPASS (texto: SEA/SEG/SES)
        if topic == "Serratec/BYPASS":
            data_aux["bypass"] = payload.strip(); return
        if topic == "Serratec/bypass/ativo":
            bypass_state["ativo"] = payload.strip() == "1"
            return

        if topic == "Serratec/bypass/central":
            bypass_state["central"] = payload.strip()
            return

        if topic == "Serratec/bypass/mode":
            bypass_state["mode"] = payload.strip() == "1"
            return

        if topic == "Serratec/bypass/setpoint":
            try:
                bypass_state["setpoint"] = float(payload)
            except:
                pass
            return
        # SETPOINT numérico
        if topic == "Serratec/SETPOINT":
            try:
                data_aux["setpoint"] = float(payload)
            except:
                pass
            return

        # SFE
        if topic == "Serratec/SFE_TEMP_OUT":
            data_aux["sfe"]["temp_out"] = float(payload); return
        if topic == "Serratec/CH/SFE/valor47":
            data_aux["sfe"]["temp_in"] = float(payload); return
        if topic == "Serratec/SFE_PRESS":
            data_aux["sfe"]["press"] = float(payload); return
        if topic == "Serratec/SFE_DELTAP":
            data_aux["sfe"]["delta"] = float(payload); return
        if topic == "Serratec/CH/SFE/KWH":
            data_aux["sfe"]["kwh"] = float(payload); return
        if topic == "Serratec/CH/SFE/valor43":
            data_aux["sfe"]["valor43"] = float(payload); return
        if topic == "Serratec/CH/SFE/valor44":
            data_aux["sfe"]["valor44"] = float(payload); return
        if topic == "Serratec/CH/SFE/valor45":
            data_aux["sfe"]["valor45"] = float(payload); return
        if topic == "Serratec/CH/SFE/valor46":
            data_aux["sfe"]["valor46"] = float(payload); return
        if topic == "Serratec/CH/SFE/valor48":
            data_aux["sfe"]["valor48"] = float(payload); return
        if topic == "Serratec/CH/SFE/valor49":
            data_aux["sfe"]["valor49"] = float(payload); return
        if topic == "Serratec/CH/SFE/valor50":
            data_aux["sfe"]["valor50"] = float(payload); return
        if topic == "Serratec/CH/SFE/valor51":
            data_aux["sfe"]["valor51"] = float(payload); return
        if topic == "Serratec/CH/SFE/valor52":
            data_aux["sfe"]["valor52"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor53":
            data_aux["sfe"]["valor53"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor54":
            data_aux["sfe"]["valor54"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor55":
            data_aux["sfe"]["valor55"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor56":
            data_aux["sfe"]["valor56"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor57":
            data_aux["sfe"]["valor57"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor58":
            data_aux["sfe"]["valor58"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor59":
            data_aux["sfe"]["valor59"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor60":
            data_aux["sfe"]["valor60"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor61":
            data_aux["sfe"]["valor61"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor62":
            data_aux["sfe"]["valor62"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor63":
            data_aux["sfe"]["valor63"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor64":
            data_aux["sfe"]["valor64"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor65":
            data_aux["sfe"]["valor65"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor66":
            data_aux["sfe"]["valor66"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor67":
            data_aux["sfe"]["valor67"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor68":
            data_aux["sfe"]["valor68"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor69":
            data_aux["sfe"]["valor69"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor70":
            data_aux["sfe"]["valor70"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor71":
            data_aux["sfe"]["valor71"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor72":
            data_aux["sfe"]["valor72"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor73":
            data_aux["sfe"]["valor73"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor74":
            data_aux["sfe"]["valor74"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor75":
            data_aux["sfe"]["valor75"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor76":
            data_aux["sfe"]["valor76"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor77":
            data_aux["sfe"]["valor77"] = payload == "1"; return
        if topic == "Serratec/CH/SFE/valor3":
            data_aux["sfe"]["valor3"] = payload == "1"; return
        # SFF
        if topic == "Serratec/SFF_TEMP_OUT":
            data_aux["sff"]["temp_out"] = float(payload); return
        if topic == "Serratec/CH/SFF/valor12":
            data_aux["sff"]["temp_in"] = float(payload); return
        if topic == "Serratec/SFF_PRESS":
            data_aux["sff"]["press"] = float(payload); return
        if topic == "Serratec/SFF_DELTAP":
            data_aux["sff"]["delta"] = float(payload); return
        if topic == "Serratec/CH/SFF/KWH":
            data_aux["sff"]["kwh"] = float(payload); return
        if topic == "Serratec/CH/SFF/valor8":
            data_aux["sff"]["valor8"] = float(payload); return
        if topic == "Serratec/CH/SFF/valor9":
            data_aux["sff"]["valor9"] = float(payload); return
        if topic == "Serratec/CH/SFF/valor10":
            data_aux["sff"]["valor10"] = float(payload); return
        if topic == "Serratec/CH/SFF/valor11":
            data_aux["sff"]["valor11"] = float(payload); return
        if topic == "Serratec/CH/SFF/valor13":
            data_aux["sff"]["valor13"] = float(payload); return
        if topic == "Serratec/CH/SFF/valor14":
            data_aux["sff"]["valor14"] = float(payload); return
        if topic == "Serratec/CH/SFF/valor15":
            data_aux["sff"]["valor15"] = float(payload); return
        if topic == "Serratec/CH/SFF/valor16":
            data_aux["sff"]["valor16"] = float(payload); return
        if topic == "Serratec/CH/SFF/valor17":
            data_aux["sff"]["valor17"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor18":
            data_aux["sff"]["valor18"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor19":
            data_aux["sff"]["valor19"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor20":
            data_aux["sff"]["valor20"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor21":
            data_aux["sff"]["valor21"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor22":
            data_aux["sff"]["valor22"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor23":
            data_aux["sff"]["valor23"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor24":
            data_aux["sff"]["valor24"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor25":
            data_aux["sff"]["valor25"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor26":
            data_aux["sff"]["valor26"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor27":
            data_aux["sff"]["valor27"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor28":
            data_aux["sff"]["valor28"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor29":
            data_aux["sff"]["valor29"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor30":
            data_aux["sff"]["valor30"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor31":
            data_aux["sff"]["valor31"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor32":
            data_aux["sff"]["valor32"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor33":
            data_aux["sff"]["valor33"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor34":
            data_aux["sff"]["valor34"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor35":
            data_aux["sff"]["valor35"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor36":
            data_aux["sff"]["valor36"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor37":
            data_aux["sff"]["valor37"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor38":
            data_aux["sff"]["valor38"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor39":
            data_aux["sff"]["valor39"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor40":
            data_aux["sff"]["valor40"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor41":
            data_aux["sff"]["valor41"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor42":
            data_aux["sff"]["valor42"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor42":
            data_aux["sff"]["valor42"] = payload == "1"; return
        if topic == "Serratec/CH/SFF/valor2":
            data_aux["sff"]["valor2"] = payload == "1"; return


        # MEDIÇÕES GERAIS
        if topic == "Serratec/AMBIENTE_TEMP":
            data_aux["geral"]["amb_temp"] = float(payload); return
        if topic == "Serratec/AGUA_TEMP":
            data_aux["geral"]["agua_temp"] = float(payload); return


        ###### SFG ###### 
        if topic == "Serratec/SFG_temp_agua":
            data_aux["sfg"]["agua_tempsfg"] = float(payload); return
        if topic == "Serratec/SFG_press_agua":
            data_aux["sfg"]["agua_presssfg"] = float(payload); return
        if topic == "Serratec/SFG_press_ar":
            data_aux["sfg"]["press_ar"] = float(payload); return
        if topic == "Serratec/SFG_temp_tanque1":
            data_aux["sfg"]["temp_tanque1"] = float(payload); return
        if topic == "Serratec/SFG_temp_tanque2":
            data_aux["sfg"]["temp_tanque2"] = float(payload); return
        if topic == "Serratec/SFG_temp_cam":
            data_aux["sfg"]["temp_cam"] = float(payload); return
        if topic == "Serratec/SFG_temp_des1":
            data_aux["sfg"]["temp_des1"] = float(payload); return
        if topic == "Serratec/SFG_temp_des2":
            data_aux["sfg"]["temp_des2"] = float(payload); return
        if topic == "Serratec/SFG_prog_lav":
            data_aux["sfg"]["prog_lav"] = (payload); return
        if topic == "Serratec/SFG_count":
            data_aux["sfg"]["count"] = (payload); return
        if topic == "Serratec/SFG_lav_cesto":
            data_aux["sfg"]["lav_cesto"] = float(payload); return
        if topic == "Serratec/SFG_nivel_tanquePass":
            data_aux["sfg"]["nivel_tanquepass"] = float(payload); return 
        if topic == "Serratec/SFG_temp_tanquePass":
            data_aux["sfg"]["temp_tanquepass"] = float(payload); return
        if topic == "Serratec/SFG_temp_pretanquePass":
            data_aux["sfg"]["temp_pretanquepass"] = float(payload); return
        if topic == "Serratec/SFG_countpass":
            data_aux["sfg"]["countpass"] = float(payload); return
        if topic == "Serratec/SFG_prog_pass":
            data_aux["sfg"]["prog_pass"] = float(payload); return
        if topic == "Serratec/SFG_lav_cestopass":
            data_aux["sfg"]["lav_cestopass"] = float(payload); return
        if topic == "Serratec/SFG_temp_campass":
            data_aux["sfg"]["temp_campass"] = float(payload); return
        if topic == "Serratec/SFG_filtro":
            data_aux["sfg"]["filtro"] = (payload); return
        if topic == "Serratec/SFG_estado":
            data_aux["sfg"]["estado"] = payload ; return
        if topic == "Serratec/SFG_estado_lub":
            data_aux["sfg"]["estado_lub"] = payload ; return
        if topic == "Serratec/SFG_manutencao":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["manutencao"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_press_cam":
            data_aux["sfg"]["press_cam"] = float(payload); return
        if topic == "Serratec/SFG_press_dest1":
            data_aux["sfg"]["press_dest1"] = float(payload); return
        if topic == "Serratec/SFG_press_dest2":
            data_aux["sfg"]["press_dest2"] = float(payload); return
        if topic == "Serratec/SFG_press_tanques":
            data_aux["sfg"]["press_tanques"] = float(payload); return
        if topic == "Serratec/SFG_press_ext":
            data_aux["sfg"]["press_ext"] = float(payload); return
        if topic == "Serratec/SFG_filtro_rest":
            data_aux["sfg"]["filtro_rest"] = float(payload); return
        if topic == "Serratec/SFG_lavauto":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["lavauto"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_lavman":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["lavman"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_lubman":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["lubman"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_lubauto":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["lubauto"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_tapeteman":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["tapeteman"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_tapeteauto":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["tapeteauto"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_porta_lav_aberta":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["porta_lav_aberta"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_porta_lav_fechada":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["porta_lav_fechada"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_porta_lub_fechada":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["porta_lub_fechada"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_porta_lub_aberta":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["porta_lub_aberta"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_func_dir":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["func_dir"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_filtrolimalhas":
            data_aux["sfg"]["filtrolimalhas"] = float(payload); return
        if topic == "Serratec/SFG_lav_camaranivel":
            data_aux["sfg"]["lav_camaranivel"] = payload ; return
        if topic == "Serratec/SFG_lub_camaranivel":
            data_aux["sfg"]["lub_camaranivel"] = payload ; return
        if topic == "Serratec/SFG_lub_tanquepress":
            data_aux["sfg"]["lub_tanquepress"] = float(payload); return
        if topic == "Serratec/SFG_still1_min":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["still1_min"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_still1_max":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["still1_max"] = payload_str in ("1", "true", "on", "yes")
            return
        
        if topic == "Serratec/SFG_still2_min":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["still2_min"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_still2_max":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["still2_max"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_tanque1_min":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["tanque1_min"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_tanque1_max":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["tanque1_max"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_tanque2_min":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["tanque2_min"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_tanque2_max":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["tanque2_max"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_tanquelub_min":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["tanquelub_min"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SFG_tanquelub_max":
            payload_str = payload.strip().lower()
            data_aux.setdefault("sfg", {})
            data_aux["sfg"]["tanquelub_max"] = payload_str in ("1", "true", "on", "yes")
            return
        
        ###### SEU ###### 
        if topic == "Serratec/SEU_temp_agua":
            data_aux["seu"]["agua_temp"] = float(payload); return
        if topic == "Serratec/SEU_press_agua":
            data_aux["seu"]["agua_press"] = float(payload); return
        if topic == "Serratec/SEU_press_ar":
            data_aux["seu"]["press_ar"] = float(payload); return
        if topic == "Serratec/SEU_temp_tanque1":
            data_aux["seu"]["temp_tanque1"] = float(payload); return
        if topic == "Serratec/SEU_temp_tanque2":
            data_aux["seu"]["temp_tanque2"] = float(payload); return
        if topic == "Serratec/SEU_temp_cam":
            data_aux["seu"]["temp_cam"] = float(payload); return
        if topic == "Serratec/SEU_temp_des1":
            data_aux["seu"]["temp_des1"] = float(payload); return
        if topic == "Serratec/SEU_temp_des2":
            data_aux["seu"]["temp_des2"] = float(payload); return
        if topic == "Serratec/SEU_prog_lav":
            data_aux["seu"]["prog_lav"] = (payload); return
        if topic == "Serratec/SEU_count":
            data_aux["seu"]["count"] = (payload); return
        if topic == "Serratec/SEU_lav_cesto":
            data_aux["seu"]["lav_cesto"] = float(payload); return
        if topic == "Serratec/SEU_nivel_tanquePass":
            data_aux["seu"]["nivel_tanquepass"] = float(payload); return 
        if topic == "Serratec/SEU_temp_tanquePass":
            data_aux["seu"]["temp_tanquepass"] = float(payload); return
        if topic == "Serratec/SEU_temp_pretanquePass":
            data_aux["seu"]["temp_pretanquepass"] = float(payload); return
        if topic == "Serratec/SEU_countpass":
            data_aux["seu"]["countpass"] = float(payload); return
        if topic == "Serratec/SEU_prog_pass":
            data_aux["seu"]["prog_pass"] = float(payload); return
        if topic == "Serratec/SEU_lav_cestopass":
            data_aux["seu"]["lav_cestopass"] = float(payload); return
        if topic == "Serratec/SEU_temp_campass":
            data_aux["seu"]["temp_campass"] = float(payload); return
        if topic == "Serratec/SEU_filtro":
            data_aux["seu"]["filtro"] = (payload); return
        if topic == "Serratec/SEU_estado":
            data_aux["seu"]["estado"] = payload ; return
        if topic == "Serratec/SEU_estado_lub":
            data_aux["seu"]["estado_lub"] = payload ; return
        if topic == "Serratec/SEU_manutencao":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["manutencao"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_press_cam":
            data_aux["seu"]["press_cam"] = float(payload); return
        if topic == "Serratec/SEU_press_dest1":
            data_aux["seu"]["press_dest1"] = float(payload); return
        if topic == "Serratec/SEU_press_dest2":
            data_aux["seu"]["press_dest2"] = float(payload); return
        if topic == "Serratec/SEU_press_tanques":
            data_aux["seu"]["press_tanques"] = float(payload); return
        if topic == "Serratec/SEU_press_ext":
            data_aux["seu"]["press_ext"] = float(payload); return
        if topic == "Serratec/SEU_filtro_rest":
            data_aux["seu"]["filtro_rest"] = float(payload); return
        if topic == "Serratec/SEU_lavauto":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["lavauto"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_lavman":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["lavman"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_lubman":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["lubman"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_lubauto":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["lubauto"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_tapeteman":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["tapeteman"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_tapeteauto":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["tapeteauto"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_porta_lav_aberta":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["porta_lav_aberta"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_porta_lav_fechada":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["porta_lav_fechada"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_porta_lub_fechada":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["porta_lub_fechada"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_porta_lub_aberta":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["porta_lub_aberta"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_func_dir":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["func_dir"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_filtrolimalhas":
            data_aux["seu"]["filtrolimalhas"] = float(payload); return
        if topic == "Serratec/SEU_lav_camaranivel":
            data_aux["seu"]["lav_camaranivel"] = payload ; return
        if topic == "Serratec/SEU_lub_camaranivel":
            data_aux["seu"]["lub_camaranivel"] = payload ; return
        if topic == "Serratec/SEU_lub_tanquepress":
            data_aux["seu"]["lub_tanquepress"] = float(payload); return
        if topic == "Serratec/SEU_still1_min":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["still1_min"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_still1_max":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["still1_max"] = payload_str in ("1", "true", "on", "yes")
            return
        
        if topic == "Serratec/SEU_still2_min":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["still2_min"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_still2_max":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["still2_max"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_tanque1_min":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["tanque1_min"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_tanque1_max":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["tanque1_max"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_tanque2_min":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["tanque2_min"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_tanque2_max":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["tanque2_max"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_tanquelub_min":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["tanquelub_min"] = payload_str in ("1", "true", "on", "yes")
            return
        if topic == "Serratec/SEU_tanquelub_max":
            payload_str = payload.strip().lower()
            data_aux.setdefault("seu", {})
            data_aux["seu"]["tanquelub_max"] = payload_str in ("1", "true", "on", "yes")
            return





    
        # -------------------------------------------------
# EXTRA (Medições adicionais dinâmicas)
# Formato correto: Serratec/EXTRA/<nome>
# -------------------------------------------------
        if topic.startswith("Serratec/EXTRA/"):
            key = topic.split("Serratec/EXTRA/")[1]
            try:
                data_aux["geral"]["extra"][key] = float(payload)
            except:
                data_aux["geral"]["extra"][key] = payload
            return

        # -------------------------------------------------
        # ESTADOS DAS MÁQUINAS
        # Formato correto: Serratec/STATE/<maquina>
        # -------------------------------------------------
        if topic.startswith("Serratec/STATE/"):
            machine = topic.split("Serratec/STATE/")[1]
            if machine in data_aux["maquinas"]:
                try:
                    data_aux["maquinas"][machine] = int(payload)
                except:
                    data_aux["maquinas"][machine] = 0
            return

        # ENERGIA
        if topic == "Serratec/ENERGIA_TOTAL":
            data_aux["energia"]["total"] = float(payload); return
        if topic == "Serratec/ENERGIA_NOW":
            data_aux["energia"]["now"] = float(payload); return
        # =================================================
        # SISTEMA MAQUINAS (igual ao teu original)
        # =================================================
        if topic.endswith("/manutencao/cmd"):
            data = json.loads(payload)
            tratar_cmd_manutencao(topic, data)
            return

        if payload.startswith("{"):
            data = json.loads(payload)
            tratar_snapshot(data)

    except Exception as e:
        print("Erro MQTT:", e)

def init_mqtt():
    global mqtt_client
    mqtt_client = mqtt.Client(client_id="dashboard_centrais")

    if MQTT_USER:
        mqtt_client.username_pw_set(MQTT_USER, MQTT_PASS)
    mqtt_client.reconnect_delay_set(min_delay=3, max_delay=10)

    mqtt_client.on_connect = on_connect
    mqtt_client.on_message = on_message
    mqtt_client.on_disconnect = on_disconnect

    mqtt_client.loop_start()
    while True:
        try:
            
            mqtt_client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60)
            
            print(f"MQTT ligado a {MQTT_BROKER}:{MQTT_PORT}")
            break
        except Exception as e:
            print("Erro MQTT, retry 5s:", e)
            time.sleep(5)

# =====================================================
# FUNCOES MAQUINAS (IGUAIS AO TEU ORIGINAL)
# =====================================================
def tratar_snapshot(data):
    nome = data.get("nome")
    if not nome:
        return
    with lock:
        dados_maquinas[nome] = data
    with lock:
        data.setdefault("online", True)
        data.setdefault("watchdog", True)
        data.setdefault("ultima_leitura", datetime.now().isoformat())
        dados_maquinas[nome] = data

def tratar_cmd_manutencao(topic, data):
    try:
        parts = topic.split("/")
        nome_maquina = parts[2]
    except Exception:
        print("Topico de manutencao inesperado:", topic)
        return

    acao = data.get("acao", "toggle")
    user_id = data.get("user_id", "desconhecido")
    ts = data.get("timestamp") or datetime.now().isoformat()

    with lock:
        if nome_maquina not in dados_maquinas:
            print(f"Maquina {nome_maquina} nao encontrada para manutencao")
            return

        m = dados_maquinas[nome_maquina]

        if acao == "on":
            m["em_manutencao"] = True
        elif acao == "off":
            m["em_manutencao"] = False
        else:
            m["em_manutencao"] = not m.get("em_manutencao", False)

        print(f"[{ts}] user={user_id} mudou manutencao de {nome_maquina} para {m['em_manutencao']}")
        salvar_log_manutencao(nome_maquina, m["em_manutencao"], user_id, ts)

def salvar_log_manutencao(nome_maquina, em_manutencao, user_id, ts_iso):
    hoje = datetime.now().strftime("%Y-%m-%d")
    filename = f"manutencao_logs_{hoje}.csv"
    novo_arquivo = not os.path.exists(filename)

    campos = ["timestamp", "nome", "em_manutencao", "user_id", "origem"]

    with open(filename, "a", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=campos)
        if novo_arquivo:
            writer.writeheader()
        writer.writerow({
            "timestamp": ts_iso,
            "nome": nome_maquina,
            "em_manutencao": em_manutencao,
            "user_id": user_id,
            "origem": "qr_code"
        })

def resumo_secao():
    resumo = {}
    with lock:
        for m in dados_maquinas.values():
            sec = m.get("secao", "DESCONHECIDO")
            if sec not in resumo:
                resumo[sec] = {
                    "Em Produção": 0,
                    "Máquina Parada": 0,
                    "Pronta a Produzir": 0,
                    "Em Carregamento": 0,
                    "Offline": 0,
                    "Desconhecido": 0
                }

            if not m.get("online", False):
                resumo[sec]["Offline"] += 1
                continue

            if m.get("tipo") == "DUAL_M" and m.get("sub"):
                raw = m["sub"]["lavagem"].get("estado_raw", 0)
                resumo[sec][ESTADOS.get(raw, "Desconhecido")] += 1
                continue

            estado = m.get("estado", "Desconhecido")
            if estado not in resumo[sec]:
                resumo[sec]["Desconhecido"] += 1
            else:
                resumo[sec][estado] += 1

    return resumo
# Adicionar no seu script Flask existente

def init_chat_database():
    """Cria tabela para o chat"""
    conn = sqlite3.connect('centrais.db')
    c = conn.cursor()
    
    c.execute('''CREATE TABLE IF NOT EXISTS chat_messages
                 (id INTEGER PRIMARY KEY AUTOINCREMENT,
                  username TEXT,
                  message TEXT,
                  timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                  central TEXT)''')
    
    conn.commit()
    conn.close()
    print("✅ Tabela de chat criada")

# Chamar a função junto com init_database()
init_chat_database()



# =====================================================
# FLASK WEB SERVER
# =====================================================
app = Flask(__name__)

# =====================================================
# ROTAS HTML
# =====================================================
@app.route("/")
def index():
    return send_from_directory(".", "index.html")

@app.route("/centrais")
def centrais():
    return send_from_directory(".", "dashboard_centrais.html")

@app.route("/SEA")
def central_sea():
    return send_from_directory(".", "dashboard_centrais.html")

@app.route("/qr")
def qr_page():
    return send_from_directory(".", "qrapp.html")
@app.route("/aux")
def aux_page():
    return send_from_directory(".", "dashboard_auxiliar.html")

@app.route("/aux/<machine>")
def aux_machine(machine):
    filename = f"{machine}.html"
    return send_from_directory("perifericos/", filename)
@app.route("/aux/supervisor")
def aux_supervisor():
    return send_from_directory(".", "Supervisor.html")

# =====================================================
# ROTAS API NOVAS (PARA AS 3 CENTRAIS)
# =====================================================
@app.route("/centrais/api/centrais", methods=["GET"])
def api_centrais():
    return jsonify([
        {
            "nome": nome,
            "descricao": config["descricao"],
            "status": data_stores[nome]["mqtt_status"]
        }
        for nome, config in CENTRAIS_CONFIG.items()
    ])

@app.route("/centrais/api/<central>/data", methods=["GET"])
def api_central_data(central):
    if central in data_stores:
        return jsonify(data_stores[central])
    return jsonify({"erro": "Central nao encontrada"}), 404

@app.route("/centrais/api/<central>/history", methods=["GET"])
def api_central_history(central):
    if central not in CENTRAIS_CONFIG:
        return jsonify({"erro": "Central nao encontrada"}), 404
    
    central_id = get_central_id(central)
    hours = request.args.get('hours', default=24, type=int)
    
    conn = sqlite3.connect('centrais.db')
    c = conn.cursor()
    time_limit = (datetime.now() - timedelta(hours=hours)).isoformat()
    
    c.execute("""SELECT timestamp, f1, f2, f3, f4, dirty, clean, treatment 
                 FROM historico 
                 WHERE central_id = ? AND timestamp > ?
                 ORDER BY timestamp ASC""", [central_id, time_limit])
    
    rows = c.fetchall()
    conn.close()
    
    result = {
        "labels": [],
        "f1": [], "f2": [], "f3": [], "f4": [],
        "dirty": [], "clean": [], "treatment": []
    }
    
    for row in rows:
        time_str = datetime.fromisoformat(row[0]).strftime("%H:%M")
        result["labels"].append(time_str)
        result["f1"].append(row[1])
        result["f2"].append(row[2])
        result["f3"].append(row[3])
        result["f4"].append(row[4])
        result["dirty"].append(row[5])
        result["clean"].append(row[6])
        result["treatment"].append(row[7])
    
    return jsonify(result)

@app.route("/centrais/api/<central>/alarms", methods=["GET"])
def api_central_alarms(central):
    if central not in CENTRAIS_CONFIG:
        return jsonify({"erro": "Central nao encontrada"}), 404
    
    central_id = get_central_id(central)
    hours = request.args.get('hours', default=24, type=int)
    
    conn = sqlite3.connect('centrais.db')
    c = conn.cursor()
    time_limit = (datetime.now() - timedelta(hours=hours)).isoformat()
    
    c.execute("""SELECT timestamp, mensagem, severidade 
                 FROM alarmes 
                 WHERE central_id = ? AND timestamp > ?
                 ORDER BY timestamp DESC
                 LIMIT 100""", [central_id, time_limit])
    
    rows = c.fetchall()
    conn.close()
    
    alarms = [{
        "timestamp": row[0],
        "message": row[1],
        "severity": row[2]
    } for row in rows]
    
    return jsonify(alarms)
@app.route("/aux/api/data", methods=["GET"])
def aux_api_data():
    response_data = data_aux.copy()
    response_data["bypass_state"] = bypass_state
    response_data["bypass_ativo"] = bypass_state["ativo"]
    response_data["bypass_central"] = bypass_state["central"]
    response_data["bypass_mode"] = bypass_state["mode"]
    return jsonify(response_data)
    #return jsonify(data_aux)

#################TEAMS###########################
@app.route("/aux/api/notify_teams", methods=["POST"])
def api_notify_teams():
    """Recebe pedidos do dashboard e publica no MQTT para o bridge Teams."""
    try:
        data = request.get_json(silent=True) or {}
        password = (data.get("password") or "").strip()
        message  = (data.get("message")  or "").strip()
        groups   = data.get("groups") or []
        username = (data.get("username") or "Dashboard").strip()

        # valida password
        if password != TEAMS_SEND_PASSWORD:
            return jsonify({"erro": "Palavra‑passe inválida"}), 403

        # valida mensagem
        if not message:
            return jsonify({"erro": "Mensagem vazia"}), 400

        # valida grupos
        groups = [g for g in groups if g in TEAMS_ALLOWED_GROUPS]
        if not groups:
            return jsonify({"erro": "Nenhum grupo válido selecionado"}), 400

        # constrói payload para MQTT
        payload = {
            "message": message,
            "groups": groups,            # ex.: ["G1","G3"]
            "username": username,
            "source": "dashboard_auxiliar",
            "ts": datetime.now().isoformat(timespec="seconds")
        }

        # publica no MQTT (bridge/Node-RED deve escutar este tópico)
        # garante que mqtt_client já está ligado (init_mqtt corre em thread)
        mqtt_client.publish("Serratec/TEAMS/NOTIFY", json.dumps(payload), qos=0, retain=False)

        return jsonify({"ok": True})
    except Exception as e:
        return jsonify({"erro": str(e)}), 500

# =====================================================
# ROTAS API LEGACY (MANTEM TEU CODIGO ORIGINAL FUNCIONANDO)
# =====================================================
@app.route("/api/maquinas", methods=["GET"])
def api_maquinas():
    with lock:
        return jsonify(dados_maquinas)

@app.route("/api/resumo", methods=["GET"])
def api_resumo():
    return jsonify(resumo_secao())

@app.route("/centrais/api/data")
def get_data():
    return jsonify(data_stores["SEA"])  # Mantem compatibilidade

@app.route("/centrais/api/health", methods=["GET"])
def api_health():
    with lock:
        return jsonify({
            nome: {
                "online": m.get("online", False),
                "watchdog": m.get("watchdog", False)
            }
            for nome, m in dados_maquinas.items()
        })

@app.route("/api/status", methods=["GET"])
def api_status():
    return jsonify({
        "script_start_iso": SCRIPT_START_ISO,
        "script_start_ts": SCRIPT_START_TS,
        "tempos_reset_count": RESET_TEMPOS_COUNT,
        "ultimo_reset_iso": ULTIMO_RESET_TEMPOS_ISO,
        "total_maquinas": len(dados_maquinas)
    })

@app.route("/api/status/reset", methods=["POST"])
def api_status_reset():
    reset_script_start()
    return jsonify({
        "ok": True,
        "script_start_iso": SCRIPT_START_ISO,
        "script_start_ts": SCRIPT_START_TS
    })

@app.route("/api/reset_tempos", methods=["POST"])
def api_reset_tempos():
    resultado = reset_tempos_acumulados()
    return jsonify({
        "ok": True,
        **resultado
    })

@app.route("/api/maquinas/<nome>/toggle_manutencao", methods=["POST"])
def api_toggle_manutencao(nome):
    with lock:
        if nome not in dados_maquinas:
            return jsonify({"erro": "Maquina nao encontrada"}), 404
        m = dados_maquinas[nome]
        m["em_manutencao"] = not m.get("em_manutencao", False)
        return jsonify({
            "nome": nome,
            "em_manutencao": m["em_manutencao"],
        })

@app.route("/api/maquinas/manutencao", methods=["POST"])
def api_manutencao_generic():
    payload = request.get_json(silent=True) or {}
    nome = payload.get("nome")
    if not nome:
        return jsonify({"erro": "Nome nao fornecido"}), 400
    with lock:
        if nome not in dados_maquinas:
            return jsonify({"erro": "Maquina nao encontrada"}), 404
        m = dados_maquinas[nome]
        m["em_manutencao"] = not m.get("em_manutencao", False)
        return jsonify({
            "nome": nome,
            "em_manutencao": m["em_manutencao"],
        })

@app.route("/api/maquinas/manutencao_qr", methods=["POST"])
def api_manutencao_qr():
    payload = request.get_json(silent=True) or {}
    nome = payload.get("nome")
    user_id = payload.get("user_id", "desconhecido")
    acao = payload.get("acao", "toggle")
    ts = datetime.now().isoformat()

    if not nome:
        return jsonify({"erro": "Nome nao fornecido"}), 400

    with lock:
        if nome not in dados_maquinas:
            return jsonify({"erro": "Maquina nao encontrada"}), 404
        m = dados_maquinas[nome]

        if acao == "on":
            m["em_manutencao"] = True
        elif acao == "off":
            m["em_manutencao"] = False
        else:
            m["em_manutencao"] = not m.get("em_manutencao", False)

        print(f"[{ts}] {user_id} alterou manutencao de {nome} para {m['em_manutencao']} (via QR)")

        return jsonify({
            "nome": nome,
            "em_manutencao": m["em_manutencao"],
            "user_id": user_id,
            "timestamp": ts
        })
@app.route("/centrais/api/chat/messages", methods=["GET"])
def get_chat_messages():
    """Busca últimas mensagens do chat"""
    central = request.args.get('central', 'geral')
    limit = request.args.get('limit', default=50, type=int)
    
    conn = sqlite3.connect('centrais.db')
    c = conn.cursor()
    
    c.execute("""SELECT username, message, timestamp, central 
                 FROM chat_messages 
                 WHERE central = ? OR central = 'geral'
                 ORDER BY timestamp DESC 
                 LIMIT ?""", (central, limit))
    
    rows = c.fetchall()
    conn.close()
    
    messages = [{
        "username": row[0],
        "message": row[1],
        "timestamp": row[2],
        "central": row[3]
    } for row in rows]
    
    return jsonify(messages[::-1])  # Inverter para ordem cronológica

@app.route("/centrais/api/chat/send", methods=["POST"])
def send_chat_message():
    """Guarda nova mensagem no banco"""
    data = request.get_json()
    
    username = data.get('username', 'Anônimo')
    message = data.get('message', '')
    central = data.get('central', 'geral')
    
    if not message:
        return jsonify({"erro": "Mensagem vazia"}), 400
    
    conn = sqlite3.connect('centrais.db')
    c = conn.cursor()
    
    c.execute("""INSERT INTO chat_messages (username, message, central) 
                 VALUES (?, ?, ?)""", (username, message, central))
    
    conn.commit()
    message_id = c.lastrowid
    conn.close()
    
    return jsonify({
        "id": message_id,
        "username": username,
        "message": message,
        "timestamp": datetime.now().isoformat(),
        "central": central
    })
# =====================================================
# API PARA CONTROLO DO BYPASS
# =====================================================
@app.route("/aux/api/control-bypass", methods=["POST"])
def api_control_bypass():
    """Recebe comandos do dashboard e publica no MQTT"""
    try:
        data = request.get_json(silent=True) or {}
        
        # Validar password
        password = (data.get("password") or "").strip()
        if password != BYPASS_PASSWORD:
            return jsonify({"error": "Palavra-passe inválida"}), 403
        
        # Validar comando
        comando = data.get("comando")
        if not comando:
            return jsonify({"error": "Comando não especificado"}), 400
        
        valor = data.get("valor")
        username = data.get("username", "Supervisor")
        
        # Processar comando
        if comando == "set_modo":
            # valor deve ser bool: true=manual, false=auto
            if not isinstance(valor, bool):
                return jsonify({"error": "Valor deve ser booleano"}), 400
            
            mqtt_client.publish(BYPASS_TOPICS["mode"], "1" if valor else "0", retain=True)
            bypass_state["mode"] = valor
            print(f"⚡ Modo bypass alterado para {'manual' if valor else 'auto'} por {username}")
            
        elif comando == "troca_centrais":
            # publica true para executar troca
            mqtt_client.publish(BYPASS_TOPICS["troca"], "1", retain=False)
            print(f"⚡ Comando troca de centrais executado por {username}")
            
        elif comando == "set_setpoint":
            # valor deve ser número
            try:
                setpoint_val = int(valor)
                if setpoint_val < 0 or setpoint_val > 1000:
                    return jsonify({"error": "Setpoint deve estar entre 0 e 1000"}), 400
                
                mqtt_client.publish(BYPASS_TOPICS["setpoint"], str(setpoint_val), retain=True)
                bypass_state["setpoint"] = setpoint_val
                print(f"⚡ Setpoint alterado para {setpoint_val} por {username}")
            except (ValueError, TypeError):
                return jsonify({"error": "Setpoint deve ser um número"}), 400
        else:
            return jsonify({"error": "Comando inválido"}), 400
        
        # Retornar estado atualizado
        return jsonify({
            "ok": True,
            "bypass_ativo": bypass_state["ativo"],
            "mode": bypass_state["mode"],
            "central": bypass_state["central"],
            "setpoint": bypass_state["setpoint"]
        })
        
    except Exception as e:
        return jsonify({"error": str(e)}), 500

# =====================================================
# MAIN
# =====================================================
if __name__ == "__main__":
    threading.Thread(target=init_mqtt, daemon=True).start()

    print("\n" + "="*60)
    print("BACKEND COMPLETO - 3 CENTRAIS (SEA, SEG, SES)")
    print("="*60)
    print("OK Teu dashboard original: http://localhost:5001/")
    print("OK Novo dashboard centrais: http://localhost:5001/centrais")
    print("OK QR code: http://localhost:5001/qr")
    print("\nTopicos MQTT:")
    print("  - Serratec/SEA_* (F1, F2, F3, F4, L, PROPRE, POT_L, POT, FGRstate, SENSOR/#)")
    print("  - Serratec/SEG_*")
    print("  - Serratec/SES_*")
    print("  - fabrica/maquinas/# (manutencao)")
    print("="*60 + "\n")

    app.run(host="0.0.0.0", port=5002, debug=False)