import snap7
from snap7.util import get_int, get_dint, get_bool, get_real
from snap7.type import Areas

from flask import Flask, jsonify, send_from_directory, send_file, request
import threading
import time
from datetime import datetime
import csv
import os
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from datetime import datetime, timedelta

# ====================== INFLUXDB (NOVO) ======================
from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS
import atexit
from datetime import datetime as dt_utc

SCRIPT_START_TS = time.time()
SCRIPT_START_ISO = datetime.now().isoformat()
# =====================================================
# RESET TEMPOS ACUMULADOS
# =====================================================
RESET_TEMPOS_COUNT = 0
ULTIMO_RESET_TEMPOS_ISO = None

# =====================================================
# REGISTO DE SCANS (QR CODE)
# =====================================================

def get_scans_filename():
    """Devolve o nome do ficheiro CSV para os scans (diário)"""
    hoje = datetime.now().strftime("%Y-%m-%d")
    return f"scans_manutencao_{hoje}.csv"

def init_scans_csv():
    """Cria o ficheiro CSV com cabeçalho se não existir"""
    filename = get_scans_filename()
    if not os.path.exists(filename):
        with open(filename, "w", newline="", encoding="utf-8") as f:
            writer = csv.writer(f)
            writer.writerow([
                "timestamp", "maquina", "device_id", "device_name", 
                "acao", "ot_number", "user", "estado_anterior", "estado_novo"
            ])
        print(f"✅ Ficheiro de scans criado: {filename}")

def registrar_scan(maquina, device_id, device_name, acao, ot_number=None, user=None, estado_anterior=None, estado_novo=None):
    """Regista um scan no ficheiro CSV"""
    filename = get_scans_filename()
    
    # Verificar se o ficheiro existe, se não criar
    if not os.path.exists(filename):
        init_scans_csv()
    
    with open(filename, "a", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow([
            datetime.now().isoformat(),
            maquina,
            device_id,
            device_name,
            acao,
            ot_number or "",
            user or "",
            estado_anterior or "",
            estado_novo or ""
        ])
    
    print(f"📱 Scan registado: {maquina} | {acao} | OT: {ot_number} | Dispositivo: {device_name} ({device_id})")

def ler_scans(maquina=None, limit=100):
    """Lê os scans do ficheiro CSV do dia atual"""
    filename = get_scans_filename()
    if not os.path.exists(filename):
        return []
    
    scans = []
    with open(filename, "r", newline="", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            if maquina and row.get("maquina", "").upper() != maquina.upper():
                continue
            scans.append(row)
    
    # Ordenar por timestamp decrescente e limitar
    scans.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
    return scans[:limit]

def ler_scans_historicos(maquina=None, limit=100, dias=7):
    """Lê scans dos últimos N dias"""
    scans = []
    
    for i in range(dias):
        data = (datetime.now() - timedelta(days=i)).strftime("%Y-%m-%d")
        filename = f"scans_manutencao_{data}.csv"
        
        if os.path.exists(filename):
            with open(filename, "r", newline="", encoding="utf-8") as f:
                reader = csv.DictReader(f)
                for row in reader:
                    if maquina and row.get("maquina", "").upper() != maquina.upper():
                        continue
                    scans.append(row)
    
    scans.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
    return scans[:limit]

def get_estatisticas_scans(dias=7):
    """Obtém estatísticas dos scans dos últimos N dias"""
    scans = ler_scans_historicos(limit=10000, dias=dias)
    
    total = len(scans)
    
    # Por máquina
    por_maquina = {}
    for scan in scans:
        maq = scan.get("maquina", "Desconhecido")
        por_maquina[maq] = por_maquina.get(maq, 0) + 1
    
    # Por dispositivo
    por_dispositivo = {}
    for scan in scans:
        disp = scan.get("device_name", "Desconhecido")
        por_dispositivo[disp] = por_dispositivo.get(disp, 0) + 1
    
    # Por ação
    por_acao = {}
    for scan in scans:
        acao = scan.get("acao", "Desconhecido")
        por_acao[acao] = por_acao.get(acao, 0) + 1
    
    # Últimas 24h
    ultimas_24h = 0
    limite = (datetime.now() - timedelta(hours=24)).isoformat()
    for scan in scans:
        if scan.get("timestamp", "") >= limite:
            ultimas_24h += 1
    
    return {
        "total_scans": total,
        "ultimas_24h": ultimas_24h,
        "por_maquina": [{"maquina": k, "total": v} for k, v in sorted(por_maquina.items(), key=lambda x: x[1], reverse=True)][:10],
        "por_dispositivo": [{"dispositivo": k, "total": v} for k, v in sorted(por_dispositivo.items(), key=lambda x: x[1], reverse=True)][:10],
        "por_acao": [{"acao": k, "total": v} for k, v in por_acao.items()]
    }

# Inicializar o ficheiro de scans
init_scans_csv()


def reset_tempos_acumulados():
    global RESET_TEMPOS_COUNT, ULTIMO_RESET_TEMPOS_ISO
    
    print(f"⏰ RESET TEMPOS ACUMULADOS #{RESET_TEMPOS_COUNT + 1}")
    
    with lock:
        total_resetadas = 0
        
        # Reset máquinas NORMAIS
        for nome, maquina in dados_maquinas.items():
            if maquina.get("tipo") != "DUAL_M":
                maquina["tempos_estado"] = {}
                total_resetadas += 1
        
        # Reset máquinas DUAL_M (sub.lavagem e sub.passivacao)
        for nome, maquina in dados_maquinas.items():
            if maquina.get("tipo") == "DUAL_M" and maquina.get("sub"):
                if maquina["sub"]["lavagem"].get("tempos_estado"):
                    maquina["sub"]["lavagem"]["tempos_estado"] = {}
                    total_resetadas += 1
                if maquina["sub"]["passivacao"].get("tempos_estado"):
                    maquina["sub"]["passivacao"]["tempos_estado"] = {}
                    total_resetadas += 1
        
        RESET_TEMPOS_COUNT += 1
        print(f"   → Resetados tempos de {total_resetadas} máquinas/subsecções")
    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
    }



INFLUX_URL = os.environ.get("INFLUX_URL", "https://eu-central-1-1.aws.cloud2.influxdata.com")
INFLUX_TOKEN = os.environ.get("INFLUX_TOKEN","4HU4sy_djDE9UUsL8dVVTJLDO73Zy8LzNz2Msid68XhpApBGrUlnOuitIeJPATNMPkqnZmvsJ4n7nrOy25DgSw==")
INFLUX_ORG = os.environ.get("INFLUX_ORG", "nodered")
INFLUX_BUCKET = os.environ.get("INFLUX_BUCKET", "producao")

influx_client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG) if INFLUX_TOKEN else None
write_api = influx_client.write_api(write_options=SYNCHRONOUS) if influx_client else None

def reset_script_start():
    global SCRIPT_START_TS, SCRIPT_START_ISO
    SCRIPT_START_TS = time.time()
    SCRIPT_START_ISO = datetime.now().isoformat()

def influx_write_snapshot(m):
    """Escreve no InfluxDB só quando chamada (vamos chamar apenas em mudanças)."""
    if not write_api:
        return

    p = (
        Point("maquinas")
        .tag("nome", m["nome"])
        .tag("secao", m["secao"])
        .tag("tipo", m.get("tipo", "NORMAL"))
        .field("estado_raw", int(m.get("estado_raw") or 0))
        .field("contador_pecas", int(m.get("contador_pecas", 0)))
        .field("online", bool(m.get("online", False)))
        .field("watchdog", bool(m.get("watchdog", False)))
        .field("em_manutencao", bool(m.get("em_manutencao", False)))
        .time(dt_utc.now())
    )

    if m.get("tipo") == "DUAL_M" and m.get("sub"):
        lav = m["sub"]["lavagem"]
        pas = m["sub"]["passivacao"]

        p = (
            p.field("lav_estado_raw", int(lav.get("estado_raw", 0)))
             .field("pas_estado_raw", int(pas.get("estado_raw", 0)))
             .field("lav_contador", int(lav.get("contador_pecas", 0)))
             .field("pas_contador", int(pas.get("contador_pecas", 0)))
             .field("programa", int(lav.get("programa", 0)))
        )

        vals = lav.get("valores", {})
        if vals.get("temperatura_agua_c") is not None:
            p = p.field("temp_agua_c", float(vals["temperatura_agua_c"]))
        if vals.get("pressao_agua_bar") is not None:
            p = p.field("pressao_agua_bar", float(vals["pressao_agua_bar"]))
        if vals.get("pressao_ar_bar") is not None:
            p = p.field("pressao_ar_bar", float(vals["pressao_ar_bar"]))

    try:
        write_api.write(bucket=INFLUX_BUCKET, org=INFLUX_ORG, record=p)
    except Exception as e:
        print("Erro a escrever Influx:", e)

@atexit.register
def _close_influx():
    try:
        if write_api:
            write_api.close()
        if influx_client:
            influx_client.close()
    except Exception:
        pass
# =====================================================

# =====================================================
# CONFIGURAÇÃO DAS MÁQUINAS
# =====================================================
MAQUINAS = [
    {"nome": "SET", "secao": "RETIFFS", "ip": "172.29.1.109", "rack": 0, "slot": 1},
    {"nome": "SF3", "secao": "RETIFFS", "ip": "172.29.1.97", "rack": 0, "slot": 1},
    {"nome": "411", "secao": "RETIFFS", "ip": "172.29.1.94", "rack": 0, "slot": 1},
    {"nome": "SEL", "secao": "RETIFFS", "ip": "172.29.1.103", "rack": 0, "slot": 1}, #alteração de ip
    {"nome": "SFJ", "secao": "RETIFFS", "ip": "172.29.1.99", "rack": 0, "slot": 1},
    {"nome": "MCK", "secao": "RETIFFS", "ip": "172.29.1.98", "rack": 0, "slot": 1},
    ################################################################################
    {"nome": "ECW", "secao": "INDEXS", "ip": "172.29.1.2", "rack": 0, "slot": 1},
    {"nome": "534", "secao": "INDEXS", "ip": "172.29.1.3", "rack": 0, "slot": 1},
    {"nome": "EC6", "secao": "INDEXS", "ip": "172.29.1.4", "rack": 0, "slot": 1},
    {"nome": "ECA", "secao": "INDEXS", "ip": "172.29.1.5", "rack": 0, "slot": 1},
    {"nome": "ECD", "secao": "INDEXS", "ip": "172.29.1.6", "rack": 0, "slot": 1},
    {"nome": "ECM", "secao": "INDEXS", "ip": "172.29.1.7", "rack": 0, "slot": 1},
    {"nome": "ECP", "secao": "INDEXS", "ip": "172.29.1.8", "rack": 0, "slot": 1},
    {"nome": "535", "secao": "INDEXS", "ip": "172.29.1.9", "rack": 0, "slot": 1},
    {"nome": "536", "secao": "INDEXS", "ip": "172.29.1.10", "rack": 0, "slot": 1},
    {"nome": "ECT", "secao": "INDEXS", "ip": "172.29.1.11", "rack": 0, "slot": 1},
    {"nome": "ECU", "secao": "INDEXS", "ip": "172.29.1.12", "rack": 0, "slot": 1},
    {"nome": "ECV", "secao": "INDEXS", "ip": "172.29.1.13", "rack": 0, "slot": 1},
    {"nome": "EC9", "secao": "INDEXS", "ip": "172.29.1.49", "rack": 0, "slot": 1},
    {"nome": "818", "secao": "INDEXS", "ip": "172.29.1.84", "rack": 0, "slot": 1},
    ################################################################################
    {"nome": "723", "secao": "MULTIS", "ip": "172.29.1.81", "rack": 0, "slot": 1},
    {"nome": "SB4", "secao": "MULTIS", "ip": "172.29.1.41", "rack": 0, "slot": 1},
    {"nome": "878", "secao": "MULTIS", "ip": "172.29.1.42", "rack": 0, "slot": 1},
    {"nome": "770", "secao": "MULTIS", "ip": "172.29.1.44", "rack": 0, "slot": 1},
    {"nome": "885", "secao": "MULTIS", "ip": "172.29.1.45", "rack": 0, "slot": 1},
    {"nome": "761", "secao": "MULTIS", "ip": "172.29.1.46", "rack": 0, "slot": 1},
    {"nome": "881", "secao": "MULTIS", "ip": "172.29.1.47", "rack": 0, "slot": 1},
    {"nome": "776", "secao": "MULTIS", "ip": "172.29.1.48", "rack": 0, "slot": 1},
    {"nome": "801", "secao": "MULTIS", "ip": "172.29.1.92", "rack": 0, "slot": 1},
    {"nome": "807", "secao": "MULTIS", "ip": "172.29.1.50", "rack": 0, "slot": 1},
    {"nome": "705", "secao": "MULTIS", "ip": "172.29.1.91", "rack": 0, "slot": 1},
    {"nome": "S02", "secao": "MULTIS", "ip": "172.29.1.82", "rack": 0, "slot": 1},
    {"nome": "758", "secao": "MULTIS", "ip": "172.29.1.83", "rack": 0, "slot": 1},
    {"nome": "867", "secao": "MULTIS", "ip": "172.29.1.85", "rack": 0, "slot": 1},
    {"nome": "872", "secao": "MULTIS", "ip": "172.29.1.86", "rack": 0, "slot": 1},
    {"nome": "716", "secao": "MULTIS", "ip": "172.29.1.87", "rack": 0, "slot": 1},
    {"nome": "BSL", "secao": "MULTIS", "ip": "172.29.1.88", "rack": 0, "slot": 1},
    {"nome": "SBF", "secao": "MULTIS", "ip": "172.29.1.89", "rack": 0, "slot": 1},
    {"nome": "754", "secao": "MULTIS", "ip": "172.29.1.90", "rack": 0, "slot": 1},
    {"nome": "327", "secao": "MULTIS", "ip": "172.29.1.132", "rack": 0, "slot": 1},
    ##################################################################################
    {"nome": "170", "secao": "HIDROMATS", "ip": "172.29.1.15", "rack": 0, "slot": 1},
    {"nome": "SEM", "secao": "HIDROMATS", "ip": "172.29.1.19", "rack": 0, "slot": 1},
    {"nome": "171", "secao": "HIDROMATS", "ip": "172.29.1.16", "rack": 0, "slot": 1},
    {"nome": "Z69", "secao": "HIDROMATS", "ip": "172.29.1.20", "rack": 0, "slot": 1},
    {"nome": "Z7C", "secao": "HIDROMATS", "ip": "172.29.1.21", "rack": 0, "slot": 1},
    {"nome": "832", "secao": "HIDROMATS", "ip": "172.29.1.18", "rack": 0, "slot": 1},
    #################################################################################
    {"nome": "PR3", "secao": "DEV", "ip": "172.29.1.108", "rack": 0, "slot": 1},
    {"nome": "ECX", "secao": "DEV", "ip": "172.29.1.14", "rack": 0, "slot": 1},

    # ===== DUAIS (Lavagem + Passivação) =====
    {"nome": "SEU", "secao": "LAVAGEM", "ip": "192.168.100.161", "rack": 0, "slot": 1, "tipo": "DUAL_M"},
    {"nome": "SFG", "secao": "LAVAGEM", "ip": "192.168.100.165", "rack": 0, "slot": 1, "tipo": "DUAL_M"},
]

# =====================================================
# MAPEAMENTO DE SENSORES (por máquina)
# =====================================================
SENSORES_DUAL_M = {
    "SEU": {
        "temp_agua": {"db": 91, "start": 38, "tipo": "DINT", "div": 10.0},
        "pressao_agua": {"db": 20, "start": 392, "tipo": "REAL"},
        "pressao_ar": {"db": 20, "start": 368, "tipo": "REAL"},
    },
    "SFG": {
        "temp_agua": {"db": 91, "start": 34, "tipo": "DINT", "div": 10.0},
        "pressao_agua": {"db": 20, "start": 368, "tipo": "REAL"},
        "pressao_ar": {"db": 20, "start": 344, "tipo": "REAL"},
    }
}

DB_NUMBER = 1
STATUS_START = 1
STATUS_SIZE = 2
CONTADOR_START = 4
CONTADOR_SIZE = 4

READ_INTERVAL = 1
WATCHDOG_TIMEOUT = 8

# =====================================================
# ESTADOS
# =====================================================
ESTADOS = {
    1: "Em Produção",
    2: "Máquina Parada",
    4: "Pronta a Produzir",
    8: "Em Carregamento",
    16: "Em Aquecimento",
    0: "Desconhecido",
}

# =====================================================
# ESTRUTURA DE DADOS
# =====================================================
plcs = {}
dados_maquinas = {}
lock = threading.Lock()

for m in MAQUINAS:
    nome = m["nome"]
    plcs[nome] = {
        "client": snap7.client.Client(),
        "ip": m["ip"],
        "rack": m["rack"],
        "slot": m["slot"],
    }
    dados_maquinas[nome] = {
        "nome": nome,
        "secao": m["secao"],
        "ip": m["ip"],

        "tipo": m.get("tipo", "NORMAL"),
        "sub": None,

        "estado_raw": None,
        "estado": "Desconhecido",
        "contador_pecas": 0,
        "online": False,
        "watchdog": False,
        "ultima_leitura": None,
        "ultima_comunicacao": None,
        "tempos_estado": {},

        "_ultimo_estado": None,
        "_ultimo_update": None,
        "_ultimo_contador": 0,

        "estado_desde_ts": None,
        "ultimo_estado_raw": None,
        "tempo_ultimo_estado_seg": 0,

        "em_manutencao": False,

        # para Influx: guardar último estado escrito
        "_last_written_estado_raw": None,
        "_last_written_contador": None,
        "_last_written_online": None,
        "_last_written_watchdog": None,
    }

    if m.get("tipo") == "DUAL_M":
        dados_maquinas[nome]["sub"] = {
            "lavagem": {
                "estado_raw": 0,
                "estado": "Desconhecido",
                "programa": 0,
                "contador_pecas": 0,
                "estado_desde_ts": None,
                "tempos_estado": {},
                "valores": {},
                "_ultimo_estado": None,
                "_ultimo_update": None,
            },
            "passivacao": {
                "estado_raw": 0,
                "estado": "Desconhecido",
                "programa": 0,
                "contador_pecas": 0,
                "estado_desde_ts": None,
                "tempos_estado": {},
                "valores": {},
                "_ultimo_estado": None,
                "_ultimo_update": None,
            }
        }

# =====================================================
# FUNÇÕES DE HISTÓRICO (CSV + PDF)
# =====================================================
def salvar_historico(maquina):
    hoje = datetime.now().strftime("%Y-%m-%d")
    filename = f"historico_{hoje}.csv"
    novo_arquivo = not os.path.exists(filename)

    campos = ["timestamp", "nome", "secao", "estado", "estado_raw",
              "contador_pecas", "programa", "online", "watchdog"]

    programa = ""
    if maquina.get("tipo") == "DUAL_M" and maquina.get("sub"):
        programa = maquina["sub"]["lavagem"].get("programa", "")

    with open(filename, "a", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=campos)
        if novo_arquivo:
            writer.writeheader()
        writer.writerow({
            "timestamp": datetime.now().isoformat(),
            "nome": maquina["nome"],
            "secao": maquina["secao"],
            "estado": maquina["estado"],
            "estado_raw": maquina["estado_raw"],
            "contador_pecas": maquina["contador_pecas"],
            "programa": programa,
            "online": maquina["online"],
            "watchdog": maquina["watchdog"]
        })

def salvar_mudanca_csv(maquina, estado_ant, estado_atual, tempo_seg, contador, timestamp_epoch):
    hoje = datetime.now().strftime("%Y-%m-%d")
    filename = f"historico_mudancas_{hoje}.csv"
    novo_arquivo = not os.path.exists(filename)

    campos = ["timestamp", "nome", "secao", "estado_anterior", "estado_atual",
              "tempo_no_estado_anterior", "contador_pecas"]

    with open(filename, "a", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=campos)
        if novo_arquivo:
            writer.writeheader()
        writer.writerow({
            "timestamp": datetime.fromtimestamp(timestamp_epoch).isoformat(),
            "nome": maquina["nome"],
            "secao": maquina["secao"],
            "estado_anterior": ESTADOS.get(estado_ant, "Desconhecido"),
            "estado_atual": ESTADOS.get(estado_atual, "Desconhecido"),
            "tempo_no_estado_anterior": f"{int(tempo_seg//3600)}h {int((tempo_seg%3600)//60)}m {int(tempo_seg%60)}s",
            "contador_pecas": contador
        })

def gerar_pdf_mudancas():
    hoje = datetime.now().strftime("%Y-%m-%d")
    csv_file = f"historico_mudancas_{hoje}.csv"
    pdf_file = f"historico_mudancas_{hoje}.pdf"

    if not os.path.exists(csv_file):
        return

    width, height = A4
    c = canvas.Canvas(pdf_file, pagesize=A4)
    c.setFont("Helvetica-Bold", 14)
    c.drawString(50, height - 50, f"Histórico de Mudanças - {hoje}")

    c.setFont("Helvetica", 9)
    y = height - 80
    linhas_por_pagina = 35
    linha_atual = 0

    with open(csv_file, "r", newline="") as f:
        reader = csv.reader(f)
        headers = next(reader)
        c.drawString(50, y, " | ".join(headers))
        y -= 14
        linha_atual += 2

        for row in reader:
            linha = " | ".join(row)
            if len(linha) > 140:
                linha = linha[:140] + "..."
            c.drawString(50, y, linha)
            y -= 14
            linha_atual += 1
            if linha_atual >= linhas_por_pagina or y < 50:
                c.showPage()
                c.setFont("Helvetica", 9)
                y = height - 50
                linha_atual = 0

    c.save()

def gerar_pdf_maquina(maquina):
    hoje = datetime.now().strftime("%Y-%m-%d")
    pdf_file = f"historico_{maquina['nome']}_{hoje}.pdf"

    width, height = A4
    c = canvas.Canvas(pdf_file, pagesize=A4)
    c.setFont("Helvetica-Bold", 14)
    c.drawString(50, height - 50, f"Histórico Máquina {maquina['nome']} - {hoje}")

    c.setFont("Helvetica", 9)
    y = height - 80

    c.drawString(50, y, "Timestamp | Estado | Contador | Programa | Online | Watchdog")
    y -= 14

    csv_file = f"historico_{hoje}.csv"
    if not os.path.exists(csv_file):
        c.drawString(50, y, "Nenhum dado disponível.")
    else:
        with open(csv_file, "r", newline="") as f:
            reader = csv.DictReader(f)
            for row in reader:
                if row.get("nome") != maquina["nome"]:
                    continue

                programa = row.get("programa", "—")
                linha = f"{row['timestamp']} | {row['estado']} | {row['contador_pecas']} | {programa} | {row['online']} | {row['watchdog']}"
                if len(linha) > 140:
                    linha = linha[:140] + "..."
                c.drawString(50, y, linha)
                y -= 14
                if y < 50:
                    c.showPage()
                    c.setFont("Helvetica", 9)
                    y = height - 50

    c.save()
    return pdf_file

# =====================================================
# THREAD DE LEITURA
# =====================================================
READ_START = min(STATUS_START, CONTADOR_START)
READ_END = max(STATUS_START + STATUS_SIZE, CONTADOR_START + CONTADOR_SIZE)
READ_SIZE = READ_END - READ_START

def ler_sensor(plc, sensor_cfg):
    db = sensor_cfg["db"]
    start = sensor_cfg["start"]
    tipo = sensor_cfg["tipo"].upper()
    data = plc.read_area(Areas.DB, db, start, 4)

    if tipo == "DINT":
        v = get_dint(data, 0)
        div = float(sensor_cfg.get("div", 1.0))
        return v / div if div else float(v)
    if tipo == "REAL":
        return float(get_real(data, 0))

    return None

def leitura_maquina(nome):
    maquina = dados_maquinas[nome]
    plc_info = plcs[nome]
    plc = plc_info["client"]

    while True:
        try:
            if not plc.get_connected():
                plc.connect(plc_info["ip"], plc_info["rack"], plc_info["slot"])

            # ===== DUAL_M =====
            if maquina.get("tipo") == "DUAL_M":
                mb950 = plc.read_area(Areas.MK, 0, 950, 1)

                # Lavagem
                lav_prod = get_bool(mb950, 0, 0)
                lav_alarm = get_bool(mb950, 0, 3)
                lav_ready = get_bool(mb950, 0, 5)

                # Passivação
                pas_prod = get_bool(mb950, 0, 1)
                pas_alarm = get_bool(mb950, 0, 4)
                pas_ready = get_bool(mb950, 0, 6)

                # MD902 (DINT) contador lavagens
                md902 = plc.read_area(Areas.MK, 0, 902, 4)
                cnt_lav = get_dint(md902, 0)

                # MW922 (INT) programa atual (comum)
                mw922 = plc.read_area(Areas.MK, 0, 922, 2)
                prog = get_int(mw922, 0)

                # DB510.DBW2 (INT) contador passivações
                db510 = plc.read_area(Areas.DB, 510, 2, 2)
                cnt_pas = get_int(db510, 0)

                # Sensores
                mapa = SENSORES_DUAL_M.get(nome, {})
                temp_agua_c = ler_sensor(plc, mapa.get("temp_agua", {"db": 0, "start": 0, "tipo": "DINT", "div": 1})) if mapa.get("temp_agua") else None
                pressao_agua_bar = ler_sensor(plc, mapa.get("pressao_agua", {"db": 0, "start": 0, "tipo": "REAL"})) if mapa.get("pressao_agua") else None
                pressao_ar_bar = ler_sensor(plc, mapa.get("pressao_ar", {"db": 0, "start": 0, "tipo": "REAL"})) if mapa.get("pressao_ar") else None

                if temp_agua_c is not None:
                    temp_agua_c = round(float(temp_agua_c), 2)
                if pressao_agua_bar is not None:
                    pressao_agua_bar = round(float(pressao_agua_bar), 2)
                if pressao_ar_bar is not None:
                    pressao_ar_bar = round(float(pressao_ar_bar), 2)

                agora = time.time()

                def estado_raw_from_bits(prod, ready, alarm):
                    if alarm:
                        return 2
                    if prod:
                        return 1
                    if ready:
                        return 4
                    return 0

                raw_lav = estado_raw_from_bits(lav_prod, lav_ready, lav_alarm)
                raw_pas = estado_raw_from_bits(pas_prod, pas_ready, pas_alarm)

                with lock:
                    maquina["online"] = True
                    maquina["watchdog"] = True
                    maquina["ultima_leitura"] = datetime.now().isoformat()
                    maquina["ultima_comunicacao"] = agora

                    sensores = {
                        "temperatura_agua_c": temp_agua_c,
                        "pressao_agua_bar": pressao_agua_bar,
                        "pressao_ar_bar": pressao_ar_bar,
                    }

                    # Lavagem
                    sub_l = maquina["sub"]["lavagem"]
                    if sub_l["_ultimo_estado"] is None:
                        sub_l["_ultimo_estado"] = raw_lav
                        sub_l["_ultimo_update"] = agora
                        sub_l["estado_desde_ts"] = agora
                    elif sub_l["_ultimo_estado"] != raw_lav:
                        dur = agora - (sub_l["_ultimo_update"] or agora)
                        k = str(sub_l["_ultimo_estado"])
                        sub_l["tempos_estado"][k] = sub_l["tempos_estado"].get(k, 0) + dur
                        sub_l["_ultimo_estado"] = raw_lav
                        sub_l["_ultimo_update"] = agora
                        sub_l["estado_desde_ts"] = agora

                    sub_l["estado_raw"] = raw_lav
                    sub_l["estado"] = ESTADOS.get(raw_lav, "Desconhecido")
                    sub_l["programa"] = prog
                    sub_l["contador_pecas"] = cnt_lav
                    sub_l["valores"].update(sensores)

                    # Passivação
                    sub_p = maquina["sub"]["passivacao"]
                    if sub_p["_ultimo_estado"] is None:
                        sub_p["_ultimo_estado"] = raw_pas
                        sub_p["_ultimo_update"] = agora
                        sub_p["estado_desde_ts"] = agora
                    elif sub_p["_ultimo_estado"] != raw_pas:
                        dur = agora - (sub_p["_ultimo_update"] or agora)
                        k = str(sub_p["_ultimo_estado"])
                        sub_p["tempos_estado"][k] = sub_p["tempos_estado"].get(k, 0) + dur
                        sub_p["_ultimo_estado"] = raw_pas
                        sub_p["_ultimo_update"] = agora
                        sub_p["estado_desde_ts"] = agora

                    sub_p["estado_raw"] = raw_pas
                    sub_p["estado"] = ESTADOS.get(raw_pas, "Desconhecido")
                    sub_p["programa"] = prog
                    sub_p["contador_pecas"] = cnt_pas
                    sub_p["valores"].update(sensores)

                    # Globais (para frontend/Influx)
                    estado_raw = raw_lav
                    contador = cnt_lav

                    # tracking global por lavagem
                    if maquina["_ultimo_estado"] is None:
                        maquina["_ultimo_estado"] = estado_raw
                        maquina["_ultimo_update"] = agora
                        maquina["estado_desde_ts"] = agora
                        maquina["ultimo_estado_raw"] = estado_raw
                        maquina["tempo_ultimo_estado_seg"] = 0
                        maquina["_ultimo_contador"] = contador
                    else:
                        if maquina["_ultimo_estado"] != estado_raw:
                            dur = agora - (maquina["_ultimo_update"] or agora)
                            maquina["ultimo_estado_raw"] = maquina["_ultimo_estado"]
                            maquina["tempo_ultimo_estado_seg"] = max(0, int(dur))
                            maquina["_ultimo_estado"] = estado_raw
                            maquina["_ultimo_update"] = agora
                            maquina["estado_desde_ts"] = agora
                        maquina["_ultimo_contador"] = contador

                    maquina["estado_raw"] = estado_raw
                    maquina["estado"] = f"Lav: {sub_l['estado']} | Pas: {sub_p['estado']}"
                    maquina["contador_pecas"] = contador

                    salvar_historico(maquina)

                    # ===== LÓGICA: escrever no Influx apenas se algo mudou =====
                    must_write = False
                    if maquina["_last_written_estado_raw"] is None:
                        must_write = True
                    else:
                        if maquina["estado_raw"] != maquina["_last_written_estado_raw"]:
                            must_write = True
                        elif maquina["contador_pecas"] != maquina["_last_written_contador"]:
                            must_write = True
                        elif maquina["online"] != maquina["_last_written_online"]:
                            must_write = True
                        elif maquina["watchdog"] != maquina["_last_written_watchdog"]:
                            must_write = True

                    snapshot = None
                    if must_write:
                        snapshot = {
                            k: v for k, v in maquina.items()
                            if not k.startswith("_")  # não enviar campos internos
                        }
                        # atualizar referência do último escrito
                        maquina["_last_written_estado_raw"] = maquina["estado_raw"]
                        maquina["_last_written_contador"] = maquina["contador_pecas"]
                        maquina["_last_written_online"] = maquina["online"]
                        maquina["_last_written_watchdog"] = maquina["watchdog"]

                # fora do lock, escreve se necessário
                if snapshot:
                    influx_write_snapshot(snapshot)

                time.sleep(READ_INTERVAL)
                continue

            # ===== MÁQUINAS NORMAIS =====
          

# lê o byte de entradas I0.x
            ib0 = plc.read_area(Areas.PE, 0, 0, 1)   # PE = inputs (entradas)
            mb7 = plc.read_area(Areas.MK, 0, 7, 1)
            i00 = get_bool(ib0, 0, 0)  # I0.0
            i06 = get_bool(ib0, 0, 6)  # I0.6
            m60 = get_bool (mb7, 0, 3)

            data = plc.db_read(DB_NUMBER, READ_START, READ_SIZE)


            estado_raw = get_int(data, STATUS_START - READ_START)
            contador = get_dint(data, CONTADOR_START - READ_START)
        
# se I0.0 == False e I0.6 == True, força "Em aquecimento"
# if (not i00) and i06:
# estado_raw = 16  # Em aquecimento
# Se o M60 do logo (M7.4) estiver ativo - > xiko
            if m60:
                estado_raw = 16


            agora = time.time()

            with lock:
                if maquina["_ultimo_estado"] is None:
                    maquina["_ultimo_estado"] = estado_raw
                    maquina["_ultimo_update"] = agora
                    maquina["estado_desde_ts"] = agora
                    maquina["ultimo_estado_raw"] = estado_raw
                    maquina["tempo_ultimo_estado_seg"] = 0
                    maquina["_ultimo_contador"] = contador
                elif maquina["_ultimo_estado"] != estado_raw:
                    dur = agora - (maquina["_ultimo_update"] or agora)

                    maquina["ultimo_estado_raw"] = maquina["_ultimo_estado"]
                    maquina["tempo_ultimo_estado_seg"] = max(0, int(dur))

                    k = str(maquina["_ultimo_estado"])
                    maquina["tempos_estado"][k] = maquina["tempos_estado"].get(k, 0) + dur

                    salvar_mudanca_csv(
                        maquina=maquina,
                        estado_ant=maquina["_ultimo_estado"],
                        estado_atual=estado_raw,
                        tempo_seg=dur,
                        contador=maquina["_ultimo_contador"],
                        timestamp_epoch=agora
                    )

                    maquina["_ultimo_estado"] = estado_raw
                    maquina["_ultimo_update"] = agora
                    maquina["estado_desde_ts"] = agora
                    maquina["_ultimo_contador"] = contador
                else:
                    maquina["_ultimo_contador"] = contador

                maquina["estado_raw"] = estado_raw
                maquina["estado"] = ESTADOS.get(estado_raw, "Desconhecido")
                maquina["contador_pecas"] = contador
                maquina["online"] = True
                maquina["watchdog"] = True
                maquina["ultima_leitura"] = datetime.now().isoformat()
                maquina["ultima_comunicacao"] = agora

                salvar_historico(maquina)

                # ===== LÓGICA: escrever no Influx apenas se algo mudou =====
                must_write = False
                if maquina["_last_written_estado_raw"] is None:
                    must_write = True
                else:
                    if maquina["estado_raw"] != maquina["_last_written_estado_raw"]:
                        must_write = True
                    elif maquina["contador_pecas"] != maquina["_last_written_contador"]:
                        must_write = True
                    elif maquina["online"] != maquina["_last_written_online"]:
                        must_write = True
                    elif maquina["watchdog"] != maquina["_last_written_watchdog"]:
                        must_write = True

                snapshot = None
                if must_write:
                    snapshot = {
                        k: v for k, v in maquina.items()
                        if not k.startswith("_")
                    }
                    maquina["_last_written_estado_raw"] = maquina["estado_raw"]
                    maquina["_last_written_contador"] = maquina["contador_pecas"]
                    maquina["_last_written_online"] = maquina["online"]
                    maquina["_last_written_watchdog"] = maquina["watchdog"]

            if snapshot:
                influx_write_snapshot(snapshot)

        except Exception:
            try:
                plc.disconnect()
            except Exception:
                pass
            with lock:
                maquina["online"] = False
                maquina["watchdog"] = False

        time.sleep(READ_INTERVAL)

# =====================================================
# WATCHDOG
# =====================================================
def watchdog():
    while True:
        time.sleep(1)
        agora = time.time()
        with lock:
            for maquina in dados_maquinas.values():
                if maquina["ultima_comunicacao"] is None or (agora - maquina["ultima_comunicacao"] > WATCHDOG_TIMEOUT):
                    maquina["online"] = False
                    maquina["watchdog"] = False

# =====================================================
# FUNÇÃO RESUMO POR SEÇÃO
# =====================================================
def resumo_secao():
    resumo = {}
    with lock:
        for m in dados_maquinas.values():
            sec = m["secao"]
            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,
                    "Em Aquecimento" : 0
                }

            if not m["online"]:
                resumo[sec]["Offline"] += 1
                continue

            if m.get("tipo") == "DUAL_M" and m.get("sub"):
                raw = m["sub"]["lavagem"]["estado_raw"]
                resumo[sec][ESTADOS.get(raw, "Desconhecido")] += 1
                continue

            resumo[sec][m["estado"]] += 1

    return resumo

# =====================================================
# FLASK WEB SERVER
# =====================================================
app = Flask(__name__)

@app.route("/")
def index():
    return send_from_directory(".", "index.html")

@app.route("/api/maquinas", methods=["GET"])
def api_maquinas():
    with lock:
        result = {}
        for nome, m in dados_maquinas.items():
            ultimo_scan = ler_scans_historicos(maquina=nome, limit=1, dias=30)
            result[nome] = {
                "nome": m["nome"],
                "secao": m["secao"],
                "ip": m["ip"],

                "tipo": m.get("tipo", "NORMAL"),
                "sub": m.get("sub"),

                "estado_raw": m["estado_raw"],
                "estado": m["estado"],
                "contador_pecas": m["contador_pecas"],
                "online": m["online"],
                "watchdog": m["watchdog"],
                "ultima_leitura": m["ultima_leitura"],
                "tempos_estado": m["tempos_estado"],

                "estado_desde_ts": m["estado_desde_ts"],
                "ultimo_estado_raw": m.get("ultimo_estado_raw"),
                "tempo_ultimo_estado_seg": m.get("tempo_ultimo_estado_seg", 0),

                "em_manutencao": m.get("em_manutencao", False),
                "ultimo_scan": ultimo_scan[0] if ultimo_scan else None
            }
        return jsonify(result)

@app.route("/api/resumo", methods=["GET"])
def api_resumo():
    return jsonify(resumo_secao())

@app.route("/api/health", methods=["GET"])
def api_health():
    with lock:
        return jsonify({nome: {"online": m["online"], "watchdog": m["watchdog"]} 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,  # ← NOVO
        "total_maquinas": len(dados_maquinas)
    })

@app.route("/api/status/reset", methods=["POST"])
def api_status_reset():
    reset_script_start()  # mantém o reset do tempo do script
    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/historico/pdf", methods=["GET"])
def api_historico_pdf():
    gerar_pdf_mudancas()
    hoje = datetime.now().strftime("%Y-%m-%d")
    pdf_file = f"historico_mudancas_{hoje}.pdf"
    if os.path.exists(pdf_file):
        return send_file(pdf_file, as_attachment=True)
    return jsonify({"erro": "PDF não encontrado"}), 404

@app.route("/api/historico/pdf/<nome>", methods=["GET"])
def api_pdf_maquina(nome):
    with lock:
        if nome not in dados_maquinas:
            return jsonify({"erro": "Máquina não encontrada"}), 404
        maquina = dados_maquinas[nome]
    pdf_file = gerar_pdf_maquina(maquina)
    if os.path.exists(pdf_file):
        return send_file(pdf_file, as_attachment=True)
    return jsonify({"erro": "PDF não gerado"}), 500
# -*- coding: utf-8 -*-
import os, re, glob, csv
from datetime import datetime
from flask import jsonify, request, send_file, Response, abort

# ============================
# Config
# ============================
# Diretório base (conforme disseste: raiz "/")
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# Ajusta o padrão consoante a tua convenção real de nomes:
# Exemplos comuns:
#   /historico_2026-03-12.csv
#   /historico_mudancas_2026-03-12.csv
#   /historico-2026-03-12.csv
PADRAO_FICHEIROS = os.path.join(BASE_DIR, "historico_mudancas_????-??-??.csv")

# Regex para extrair YYYY-MM-DD de um nome de ficheiro
RE_DATA = re.compile(r"(\d{4}-\d{2}-\d{2})")

ESTADO_MAP_NUM_TO_STR = {
    "1": "Em Produção",
    "2": "Máquina Parada",
    "4": "Pronta a Produzir",
    "8": "Em Carregamento",
    "16": "Em Aquecimento"
}
UP = {"Em Produção", "Pronta a Produzir", "Em Carregamento"}
DOWN = {"Máquina Parada"}

# ============================
# Helpers
# ============================

def _listar_arquivos():
    """Devolve lista de caminhos absolutos dos CSV que batem no padrão."""
    # glob pode ser pesado na raiz. Se possível, aponta para uma pasta dedicada (recomendado).
    files = glob.glob(PADRAO_FICHEIROS)
    return [f for f in files if os.path.isfile(f)]

def _extrair_data_from_filename(path):
    """Tenta extrair YYYY-MM-DD do nome do ficheiro."""
    base = os.path.basename(path)
    m = RE_DATA.search(base)
    return m.group(1) if m else None

def _resolver_csv_por_data(data_str):
    """
    Valida o formato da data e encontra o ficheiro que contenha essa data no nome.
    Retorna caminho absoluto ou None.
    """
    try:
        datetime.strptime(data_str, "%Y-%m-%d")
    except ValueError:
        return None

    candidatos = _listar_arquivos()
    for p in candidatos:
        if data_str in os.path.basename(p):
            return p
    return None

def _parse_timestamp(ts_raw):
    """
    Tenta converter timestamp (string) num datetime. Suporta ISO e variações.
    """
    if isinstance(ts_raw, datetime):
        return ts_raw
    if not ts_raw:
        return None
    # Tentativas comuns
    for fmt in ("%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S",
                "%Y-%m-%d %H:%M:%S", "%Y/%m/%d %H:%M:%S"):
        try:
            return datetime.strptime(str(ts_raw), fmt)
        except Exception:
            pass
    # Fallback: tenta com fromisoformat
    try:
        return datetime.fromisoformat(str(ts_raw))
    except Exception:
        return None

def _estado_humano(estado_val):
    """
    Converte '1/2/4/8/16' -> label; caso já venha string, devolve como está.
    """
    if estado_val is None:
        return "Desconhecido"
    s = str(estado_val).strip()
    if s.isdigit():
        return ESTADO_MAP_NUM_TO_STR.get(s, "Desconhecido")
    return s or "Desconhecido"

# ============================
# 1) Listar datas
# ============================

@app.route("/api/historico/datas", methods=["GET"])
def api_historico_listar_datas():
    """
    Devolve as datas (YYYY-MM-DD) que existem nos ficheiros de histórico,
    ordenadas desc (mais recente primeiro), sem duplicados.
    """
    paths = _listar_arquivos()
    datas = []
    seen = set()
    for p in paths:
        d = _extrair_data_from_filename(p)
        if d and d not in seen:
            seen.add(d)
            datas.append(d)
    # Ordena descendente por data
    try:
        datas.sort(key=lambda x: datetime.strptime(x, "%Y-%m-%d"), reverse=True)
    except Exception:
        datas.sort(reverse=True)

    return jsonify(datas)

# ============================
# 2) Servir CSV por data
# ============================

@app.route("/api/historico/csv", methods=["GET"])
def api_historico_csv():
    """
    Recebe ?data=YYYY-MM-DD e devolve o CSV correspondente (streaming).
    Opcional: ?filename=historico_2026-03-12.csv
    """
    data = request.args.get("data", type=str)
    filename_qs = request.args.get("filename", type=str)

    if filename_qs:
        # Validação simples: impede path traversal.
        base = os.path.basename(filename_qs)
        path = os.path.join(BASE_DIR, base)
        print("DEBUG: procurando CSV em:", path)
        if not os.path.isfile(path) or not RE_DATA.search(base):
            return jsonify({"erro": "Ficheiro inválido"}), 400
    else:
        if not data:
            return jsonify({"erro": "Falta 'data' (YYYY-MM-DD)"}), 400
        path = _resolver_csv_por_data(data)
        if not path:
            return jsonify({"erro": "CSV não encontrado para a data"}), 404

    # Envia inline (sem attachment) para o front fazer streaming/parsing
    return send_file(path, mimetype="text/csv", as_attachment=False)

# ============================
# 3) (Opcional) KPIs no servidor
# ============================

@app.route("/api/historico")
def historico_page():
    return send_file("historico.html")

@app.route("/api/historico/kpis", methods=["GET"])
def api_historico_kpis():
    """
    KPIs por máquina numa data: ?data=YYYY-MM-DD&maquina=SB4
    Calcula Disponibilidade, MTBF, MTTR, tempos por estado e timeline agregada.
    """
    data = request.args.get("data", type=str)
    maquina = request.args.get("maquina", type=str)

    if not data or not maquina:
        return jsonify({"erro": "Parâmetros obrigatórios: data, maquina"}), 400

    path = _resolver_csv_por_data(data)
    if not path:
        return jsonify({"erro": "CSV não encontrado para a data"}), 404

    # Lê e filtra a máquina
    eventos = []
    with open(path, "r", newline="", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        # normaliza nomes de colunas
        cols = {c.lower().strip(): c for c in reader.fieldnames}
        get = lambda k, *alt: cols.get(k) or next((cols.get(a) for a in alt if cols.get(a)), None)

        c_ts   = get("timestamp", "data", "datetime", "hora")
        c_nome = get("nome", "maquina", "equipamento", "machine")
        c_estado = get("estado_atual", "estado", "estadoactual", "estado_atualizado")
        c_pecas  = get("contador_pecas", "contagem", "contador", "pecas", "peças", "part_count")

        if not (c_ts and c_nome and c_estado):
            return jsonify({"erro": "Cabeçalhos de CSV não reconhecidos"}), 400

        alvo = str(maquina).strip().upper()
        for row in reader:
            nm = str(row.get(c_nome, "")).strip().upper()
            if nm != alvo:
                continue
            ts = _parse_timestamp(row.get(c_ts))
            if not ts:
                continue
            estado = _estado_humano(row.get(c_estado))
            pecas = row.get(c_pecas)
            try:
                pecas = int(pecas) if pecas not in (None, "", "None") else None
            except Exception:
                pecas = None

            eventos.append({
                "timestamp": ts,
                "estado_atual": estado,
                "contador_pecas": pecas
            })

    if not eventos:
        return jsonify({"erro": "Sem eventos para a máquina nessa data"}), 404

    # Ordena e cria intervalos
    eventos.sort(key=lambda x: x["timestamp"])
    enriched = []
    for i, e in enumerate(eventos):
        next_ts = eventos[i+1]["timestamp"] if i+1 < len(eventos) else None
        if next_ts:
            dur = max(0.0, (next_ts - e["timestamp"]).total_seconds())
            enriched.append({
                "t_ini": e["timestamp"].isoformat(),
                "t_fim": next_ts.isoformat(),
                "estado_atual": e["estado_atual"],
                "dur_sec": dur
            })

    # Agregações
    por_estado = {}
    for e in enriched:
        por_estado[e["estado_atual"]] = por_estado.get(e["estado_atual"], 0.0) + e["dur_sec"]

    up = sum(s for est, s in por_estado.items() if est in UP)
    down = sum(s for est, s in por_estado.items() if est in DOWN)
    total = up + down if (up + down) > 0 else 0.0

    # Falhas e reparos (transições UP->DOWN, DOWN->UP)
    states = [e["estado_atual"] for e in enriched]
    failures = sum(1 for a, b in zip(states[:-1], states[1:]) if (a in UP and b in DOWN))
    repairs  = sum(1 for a, b in zip(states[:-1], states[1:]) if (a in DOWN and b in UP))

    availability = (up / total) if total > 0 else None
    mtbf_sec = (up / failures) if failures > 0 else None
    mttr_sec = (down / repairs) if repairs > 0 else None

    # Série de peças
    com_pecas = [e for e in eventos if e["contador_pecas"] is not None]
    com_pecas_sorted = sorted(com_pecas, key=lambda x: x["timestamp"])
    pecas_series = [{"t": e["timestamp"].isoformat(), "y": e["contador_pecas"]} for e in com_pecas_sorted]

    return jsonify({
        "data": data,
        "maquina": maquina,
        "availability": availability,
        "mtbf_sec": mtbf_sec,
        "mttr_sec": mttr_sec,
        "failures": failures,
        "repairs": repairs,
        "por_estado": por_estado,     # dict: estado -> seg
        "timeline": enriched,         # lista de intervalos t_ini, t_fim, estado, dur_sec
        "pecas": pecas_series         # série de contador de peças
    })
@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": "Máquina não encontrada"}), 404
        
        m = dados_maquinas[nome]
        estado_anterior = m.get("em_manutencao", False)
        
        # Obter dados do POST (do Android)
        data = request.get_json(silent=True) or {}
        ativar = data.get("ativar")  # bool: true = ativar, false = desativar
        ot_number = data.get("ot_number")  # string: número da OT
        device_id = data.get("device_id")  # string: ID do dispositivo Android
        device_name = data.get("device_name")  # string: nome do dispositivo
        user = data.get("user")  # string: utilizador (opcional)
        
        # Determinar ação
        if ativar is not None:
            m["em_manutencao"] = ativar
            acao = "ATIVAR" if ativar else "DESATIVAR"
        else:
            m["em_manutencao"] = not estado_anterior
            acao = "TOGGLE"
        
        # Registrar o scan no CSV
        registrar_scan(
            maquina=nome,
            device_id=device_id,
            device_name=device_name,
            acao=acao,
            ot_number=ot_number,
            user=user,
            estado_anterior="Em Manutenção" if estado_anterior else "Em Produção",
            estado_novo="Em Manutenção" if m["em_manutencao"] else "Em Produção"
        )
        
        return jsonify({
            "success": True,
            "nome": nome,
            "em_manutencao": m["em_manutencao"],
            "secao": m.get("secao", "Desconhecido"),
            "estado": m.get("estado", "Desconhecido"),
            "acao": acao,
            "ot_number": ot_number,
            "device_id": device_id,
            "device_name": device_name,
            "timestamp": datetime.now().isoformat()
        })

@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 não fornecido"}), 400
    with lock:
        if nome not in dados_maquinas:
            return jsonify({"erro": "Máquina não 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/scans/historico", methods=["GET"])
def api_scans_historico():
    """Consulta o histórico de scans de manutenção"""
    maquina = request.args.get("maquina")
    limit = request.args.get("limit", default=100, type=int)
    dias = request.args.get("dias", default=7, type=int)
    
    scans = ler_scans_historicos(maquina=maquina, limit=limit, dias=dias)
    return jsonify(scans)

@app.route("/api/scans/ultimo/<maquina>", methods=["GET"])
def api_scans_ultimo(maquina):
    """Obtém o último scan de uma máquina específica"""
    scans = ler_scans_historicos(maquina=maquina, limit=1, dias=30)
    if scans:
        return jsonify(scans[0])
    return jsonify({"erro": "Nenhum scan encontrado para esta máquina"}), 404

@app.route("/api/scans/estatisticas", methods=["GET"])
def api_scans_estatisticas():
    """Estatísticas dos scans de manutenção"""
    dias = request.args.get("dias", default=7, type=int)
    stats = get_estatisticas_scans(dias=dias)
    return jsonify(stats)

@app.route("/api/scans/hoje", methods=["GET"])
def api_scans_hoje():
    """Scans do dia atual"""
    scans = ler_scans(limit=500)
    return jsonify(scans)

# =====================================================
# MAIN
# =====================================================
if __name__ == "__main__":
    for nome in dados_maquinas.keys():
        threading.Thread(target=leitura_maquina, args=(nome,), daemon=True).start()
    threading.Thread(target=watchdog, daemon=True).start()

    print("WebServer ativo em http://0.0.0.0:5000")
    app.run(host="0.0.0.0", port=5000)
