

// === Início de: ./package.json ===
{
  "name": "evse-backend",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "axios": "^1.13.2",
    "bcryptjs": "^3.0.2",
    "dotenv": "^16.5.0",
    "express": "^5.1.0",
    "express-rate-limit": "^8.2.1",
    "express-validator": "^7.2.1",
    "jsonwebtoken": "^9.0.2",
    "knex": "^3.1.0",
    "mqtt": "^5.13.1",
    "pg": "^8.16.0",
    "socket.io": "^4.8.1",
    "web-push": "^3.6.7"
  },
  "devDependencies": {
    "nodemon": "^3.1.10"
  }
}

// === Fim de: ./package.json ===


// === Início de: ./server.js ===
// server.js
const http = require('http');
const { Server } = require('socket.io');
const app = require('./app');
const db = require('./db');
const jwt = require('jsonwebtoken');
require('dotenv').config();

if (!process.env.JWT_SECRET) {
  throw new Error('JWT_SECRET não definido no .env');
}

if (!process.env.MQTT_URL) {
  console.warn('Warning: MQTT_URL is not defined.');
}

const server = http.createServer(app);

const origins = process.env.CORS_ORIGIN
  ? process.env.CORS_ORIGIN.split(',').map((s) => s.trim())
  : ['http://localhost:5173'];

const io = new Server(server, {
  cors: {
    origin: origins,
    methods: ['GET', 'POST'],
    credentials: true,
  },
});

const { on } = require('./mqtt/client');
console.log('MQTT client initialized.');

// ---------------------------
// Helpers de normalização
// ---------------------------
const toNum = (v) => {
  if (v === null || v === undefined || v === '') return 0;
  const n = typeof v === 'number' ? v : parseFloat(v);
  return Number.isFinite(n) ? n : 0;
};

const toArr3 = (v) => {
  if (Array.isArray(v)) {
    return [toNum(v[0]), toNum(v[1]), toNum(v[2])];
  }
  // se vier como objeto {l1,l2,l3}
  if (v && typeof v === 'object') {
    return [toNum(v.l1), toNum(v.l2), toNum(v.l3)];
  }
  return [0, 0, 0];
};

const normalizeStatus = (rawStatus) => {
  const s = String(rawStatus || '').toLowerCase();

  if (s.includes('charging')) return '⚡ Charging';
  if (s.includes('ready')) return '🟢 Ready';
  if (s.includes('fault') || s.includes('error')) return '⚠️ Fault';
  if (s.includes('wait')) return '⚡ Wait';
  if (s.includes('not conn') || s.includes('disconnected')) return '🔌 Not Conn.';
  if (s.includes('vent')) return '💨 Vent';

  // fallback: devolve string original se não bater em nada
  return rawStatus || '—';
};

// Normaliza eventos de status (realtime)
function normalizeChargingStatus(data = {}) {
  const chargerId = data.charger_id || data.chargerId || data.id;

  const powerArr = toArr3(data.power || data.raw?.power);
  const voltageArr = toArr3(data.voltage || data.raw?.voltage);
  const currentArr = toArr3(data.current || data.raw?.current);

  const status =
    normalizeStatus(data.status || data.state || data.raw?.state);

  const chargingTime =
    toNum(data.charging_time) ||
    toNum(data.chargingTime) ||
    toNum(data.raw?.chargingTime) ||
    toNum(data.raw?.sessionTime);

  const consumption =
    toNum(data.consumption) ||
    toNum(data.raw?.consumption);

  const chargingCurrent =
    toNum(data.charging_current) ||
    toNum(data.chargingCurrent) ||
    currentArr[0];

  return {
    charger_id: chargerId,
    mqtt_topic: data.mqtt_topic || data.mqttTopic,

    status,
    stateCode: data.stateCode || data.raw?.stateCode || undefined,

    consumption,
    charging_time: chargingTime,
    charging_current: chargingCurrent,

    power: powerArr,
    voltage: voltageArr,
    current: currentArr,

    raw: data.raw || data, // mantém raw p/ debug, mas já limpinho
    updated_at: new Date().toISOString(),
  };
}

// Normaliza eventos de config (quando o carregador manda config)
function normalizeChargingConfig(data = {}) {
  const chargerId = data.charger_id || data.chargerId || data.id;

  // se vierem chaves diferentes, mapeia
  const cfg = data.config || data.raw?.config || data;

  return {
    charger_id: chargerId,
    mqtt_topic: data.mqtt_topic || data.mqttTopic,
    config: {
      max_charging_current:
        cfg.max_charging_current ??
        cfg.maxChargingCurrent ??
        cfg.max_current ??
        cfg.maxCurrent ??
        undefined,

      require_auth:
        cfg.require_auth ??
        cfg.requireAuth ??
        undefined,

      rcm_enabled:
        cfg.rcm_enabled ??
        cfg.rcmEnabled ??
        undefined,

      temperature_limit:
        cfg.temperature_limit ??
        cfg.temperatureThreshold ??
        cfg.temp_limit ??
        undefined,
    },
    raw: data.raw || data,
    updated_at: new Date().toISOString(),
  };
}

// ---------------------------
// auth middleware do socket
// ---------------------------
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (!token) return next(new Error('Authentication error: token required'));

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    socket.user = payload;
    next();
  } catch (err) {
    next(new Error('Authentication error'));
  }
});

io.on('connection', (socket) => {
  console.log(`Client connected: ${socket.id}, user: ${socket.user.username}`);

  // join rooms apenas do user autenticado
  socket.on('joinChargers', async (chargerIds = []) => {
    try {
      if (!Array.isArray(chargerIds) || chargerIds.length === 0) return;

      const rows = await db('chargers')
        .whereIn('id', chargerIds)
        .andWhere({ user_id: socket.user.id })
        .select('id');

      const allowed = rows.map((r) => r.id);
      allowed.forEach((id) => socket.join(id));

      console.log(`Socket ${socket.id} joined chargers: ${allowed}`);
    } catch (err) {
      console.error('joinChargers error:', err);
    }
  });

  socket.on('charger-action', ({ chargerId, action, ampLimit }) => {
    console.log(
      `Received action "${action}" for charger ${chargerId} by user ${socket.user.id}`
    );
    io.to(chargerId).emit('charger-action-status', 'success');
  });

  socket.on('disconnect', (reason) => {
    console.log(`Client disconnected: ${socket.id}, reason: ${reason}`);
  });
});

// ---------------------------
// Relay MQTT -> Socket.IO (NORMALIZADO)
// ---------------------------
on('charging-status', (data) => {
  const normalized = normalizeChargingStatus(data);
  const chargerId = normalized.charger_id;
  if (!chargerId) return;

  io.to(chargerId).emit('charging-status', normalized);
});

on('charging-config', (data) => {
  const normalized = normalizeChargingConfig(data);
  const chargerId = normalized.charger_id;
  if (!chargerId) return;

  io.to(chargerId).emit('charging-config', normalized);
});

const PORT = process.env.PORT || 4000;
server.listen(PORT, () => {
  console.log(`Server listening on http://localhost:${PORT}`);
});

// === Fim de: ./server.js ===
