

// === Início de: ./utils/pushService.js ===
// utils/pushService.js
const webpush = require('web-push');
const db = require('../db');

const hasVapid =
  !!process.env.VAPID_PUBLIC_KEY && !!process.env.VAPID_PRIVATE_KEY;

if (!hasVapid) {
  console.warn('[Push] VAPID keys não definidas. Push desativado.');
} else {
  webpush.setVapidDetails(
    process.env.VAPID_SUBJECT || 'mailto:admin@evstation.local',
    process.env.VAPID_PUBLIC_KEY,
    process.env.VAPID_PRIVATE_KEY
  );
}

// retry simples p/ 429 e 5xx
async function sendWithRetry(subscription, message, tries = 2) {
  try {
    return await webpush.sendNotification(subscription, message);
  } catch (err) {
    const code = err?.statusCode;
    if ((code === 429 || code >= 500) && tries > 1) {
      await new Promise((r) => setTimeout(r, 1000));
      return sendWithRetry(subscription, message, tries - 1);
    }
    throw err;
  }
}

async function sendPushToUser(userId, payload) {
  if (!hasVapid) return;

  const subs = await db('push_subscriptions')
    .where({ user_id: userId })
    .select('id', 'endpoint', 'p256dh', 'auth');

  if (!subs.length) return;

  const message = JSON.stringify(payload);

  await Promise.allSettled(
    subs.map(async (s) => {
      const subscription = {
        endpoint: s.endpoint,
        keys: {
          p256dh: s.p256dh,
          auth: s.auth,
        },
      };

      try {
        await sendWithRetry(subscription, message);

        // TTL opcional (default 15min)
        // se quiseres usar TTL do payload, descomenta e passa options no sendWithRetry
        // await webpush.sendNotification(subscription, message, {
        //   TTL: payload.ttl ?? 60 * 15,
        // });
      } catch (err) {
        const code = err?.statusCode;

        // remove subscriptions mortas
        if (code === 404 || code === 410) {
          await db('push_subscriptions').where({ id: s.id }).del();
        } else {
          console.error('[Push] erro ao enviar:', err.message);
        }
      }
    })
  );
}

module.exports = { sendPushToUser };

// === Fim de: ./utils/pushService.js ===


// === Início de: ./routes/charger_sessions.js ===
// routes/charger_sessions.js
const express = require('express');
const { param, query, body, validationResult } = require('express-validator');
const router = express.Router();
const verifyToken = require('../middleware/verifyToken');
const db = require('../db');

router.use(verifyToken);

function handleValidation(req, res, next) {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ success: false, errors: errors.array() });
  }
  next();
}

// GET /api/charger_sessions?chargerId=...
router.get(
  '/',
  [query('chargerId').isUUID().withMessage('chargerId deve ser UUID válido')],
  handleValidation,
  async (req, res) => {
    const { chargerId } = req.query;
    const userId = req.user.id;

    const charger = await db('chargers')
      .where({ id: chargerId, user_id: userId })
      .first();

    if (!charger) {
      return res
        .status(403)
        .json({ success: false, message: 'Acesso não autorizado' });
    }

    const sessions = await db('charger_sessions')
      .where({ charger_id: chargerId })
      .orderBy('started_at', 'desc');

    res.json({ success: true, data: sessions });
  }
);

// GET /api/charger_sessions/:id
router.get(
  '/:id',
  [param('id').isUUID().withMessage('ID de sessão inválido')],
  handleValidation,
  async (req, res) => {
    const { id } = req.params;
    const userId = req.user.id;

    const session = await db('charger_sessions')
      .join('chargers', 'charger_sessions.charger_id', 'chargers.id')
      .where({ 'charger_sessions.id': id, 'chargers.user_id': userId })
      .select('charger_sessions.*')
      .first();

    if (!session) {
      return res
        .status(404)
        .json({ success: false, message: 'Sessão não encontrada' });
    }

    res.json({ success: true, data: session });
  }
);

// POST /api/charger_sessions
router.post(
  '/',
  [body('charger_id').isUUID().withMessage('charger_id deve ser UUID válido')],
  handleValidation,
  async (req, res) => {
    const { charger_id } = req.body;
    const userId = req.user.id;

    const charger = await db('chargers')
      .where({ id: charger_id, user_id: userId })
      .first();

    if (!charger) {
      return res
        .status(403)
        .json({ success: false, message: 'Acesso não autorizado' });
    }

    const [inserted] = await db('charger_sessions')
      .insert({ charger_id, started_at: new Date() })
      .returning('*');

    res.status(201).json({ success: true, data: inserted });
  }
);

// PUT /api/charger_sessions/:id
router.put(
  '/:id',
  [
    param('id').isUUID().withMessage('ID de sessão inválido'),
    body('ended_at').optional().isISO8601().toDate(),
    body('kwh').optional().isFloat({ min: 0 }),
    body('cost').optional().isFloat({ min: 0 }),
  ],
  handleValidation,
  async (req, res) => {
    const { id } = req.params;
    const { ended_at, kwh, cost } = req.body;
    const userId = req.user.id;

    const session = await db('charger_sessions')
      .join('chargers', 'charger_sessions.charger_id', 'chargers.id')
      .where({ 'charger_sessions.id': id, 'chargers.user_id': userId })
      .first();

    if (!session) {
      return res
        .status(404)
        .json({ success: false, message: 'Sessão não encontrada' });
    }

    const [updated] = await db('charger_sessions')
      .where({ id })
      .update({ ended_at, kwh, cost })
      .returning('*');

    res.json({ success: true, data: updated });
  }
);

// DELETE /api/charger_sessions/:id
router.delete(
  '/:id',
  [param('id').isUUID().withMessage('ID de sessão inválido')],
  handleValidation,
  async (req, res) => {
    const { id } = req.params;
    const userId = req.user.id;

    const deleted = await db('charger_sessions')
      .join('chargers', 'charger_sessions.charger_id', 'chargers.id')
      .where({ 'charger_sessions.id': id, 'chargers.user_id': userId })
      .del();

    if (!deleted) {
      return res
        .status(404)
        .json({ success: false, message: 'Sessão não encontrada' });
    }

    res.json({ success: true, message: 'Sessão excluída com sucesso' });
  }
);

// GET /api/charger_sessions/history/:chargerId
router.get(
  '/history/:chargerId',
  [
    param('chargerId').isUUID().withMessage('chargerId deve ser UUID válido'),
    query('viewMode')
      .isIn(['Day', 'Week', 'Month'])
      .withMessage('viewMode inválido'),
  ],
  handleValidation,
  async (req, res) => {
    const { chargerId } = req.params;
    const { viewMode } = req.query;
    const userId = req.user.id;

    // ownership check
    const charger = await db('chargers')
      .where({ id: chargerId, user_id: userId })
      .first();

    if (!charger) {
      return res
        .status(403)
        .json({ success: false, message: 'Acesso não autorizado' });
    }

    let qb = db('charger_sessions')
      .where({ charger_id: chargerId })
      .sum('kwh as total_kwh');

    switch (viewMode) {
      case 'Day':
        qb = qb
          .select(db.raw('DATE(started_at) AS period'))
          .groupBy(db.raw('DATE(started_at)'))
          .orderBy('period', 'desc');
        break;

      case 'Week':
        qb = qb
          .select(
            db.raw('EXTRACT(ISOYEAR FROM started_at) AS y'),
            db.raw('EXTRACT(WEEK FROM started_at) AS w'),
            db.raw(
              "EXTRACT(ISOYEAR FROM started_at)||'-'||LPAD(EXTRACT(WEEK FROM started_at)::text,2,'0') AS period"
            )
          )
          .groupBy('y', 'w')
          .orderBy([{ column: 'y', order: 'desc' }, { column: 'w', order: 'desc' }]);
        break;

      case 'Month':
        qb = qb
          .select(
            db.raw('EXTRACT(YEAR FROM started_at) AS y'),
            db.raw('EXTRACT(MONTH FROM started_at) AS m'),
            db.raw(
              "EXTRACT(YEAR FROM started_at)||'-'||LPAD(EXTRACT(MONTH FROM started_at)::text,2,'0') AS period"
            )
          )
          .groupBy('y', 'm')
          .orderBy([{ column: 'y', order: 'desc' }, { column: 'm', order: 'desc' }]);
        break;
    }

    const rows = await qb;

    // ✅ devolve lista vazia em vez de 404
    if (!rows.length) {
      return res.json({ success: true, data: [] });
    }

    const data = rows.map((r) => ({
      started_at: r.period,
      kwh: parseFloat(r.total_kwh) || 0,
    }));

    res.json({ success: true, data });
  }
);

module.exports = router;

// === Fim de: ./routes/charger_sessions.js ===


// === Início de: ./routes/push.js ===
// routes/push.js
const express = require('express');
const { body, validationResult } = require('express-validator');
const verifyToken = require('../middleware/verifyToken');
const db = require('../db');
const { sendPushToUser } = require('../utils/pushService');

const router = express.Router();
router.use(verifyToken);

function handleValidation(req, res, next) {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ success: false, errors: errors.array() });
  }
  next();
}

// GET /api/push/vapid-public-key
router.get('/vapid-public-key', (req, res) => {
  if (!process.env.VAPID_PUBLIC_KEY) {
    return res
      .status(503)
      .json({ success: false, message: 'Push indisponível' });
  }
  res.json({ success: true, data: { key: process.env.VAPID_PUBLIC_KEY } });
});

// POST /api/push/subscribe
router.post(
  '/subscribe',
  [
    body('endpoint').isString(),
    body('keys.p256dh').isString(),
    body('keys.auth').isString(),
  ],
  handleValidation,
  async (req, res) => {
    const userId = req.user.id;
    const { endpoint, keys } = req.body;
    const ua = req.headers['user-agent'] || null;

    // evita duplicados
    const existing = await db('push_subscriptions')
      .where({ endpoint, user_id: userId })
      .first();

    if (existing) {
      return res.json({ success: true, data: existing });
    }

    const [inserted] = await db('push_subscriptions')
      .insert({
        user_id: userId,
        endpoint,
        p256dh: keys.p256dh,
        auth: keys.auth,
        user_agent: ua,
        created_at: new Date().toISOString(),
      })
      .returning('*');

    res.status(201).json({ success: true, data: inserted });
  }
);

// POST /api/push/unsubscribe
router.post(
  '/unsubscribe',
  [body('endpoint').optional().isString()],
  handleValidation,
  async (req, res) => {
    const userId = req.user.id;
    const { endpoint } = req.body || {};

    // se não houver sub no navegador, responde ok
    if (!endpoint) {
      return res.json({ success: true, message: 'No subscription' });
    }

    await db('push_subscriptions')
      .where({ endpoint, user_id: userId })
      .del();

    res.json({ success: true, message: 'Unsubscribed' });
  }
);

// POST /api/push/test
router.post('/test', async (req, res) => {
  const userId = req.user.id;
  await sendPushToUser(userId, {
    title: '📬 Teste EV Station',
    body: 'Push notifications estão a funcionar!',
    url: '/',
  });

  res.json({ success: true, message: 'Push enviado' });
});

module.exports = router;

// === Fim de: ./routes/push.js ===


// === Início de: ./routes/users.js ===
const express = require('express');
const router = express.Router();
const db = require('../db');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const rateLimit = require('express-rate-limit');

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

// limiter só para auth
const authLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 10,
  standardHeaders: true,
  legacyHeaders: false,
});

// POST /api/users/login
router.post('/login', authLimiter, async (req, res) => {
  const { username, password } = req.body;

  if (!username || !password) {
    return res
      .status(400)
      .json({ success: false, message: 'Usuário e senha são obrigatórios' });
  }

  try {
    const user = await db('users').where({ username }).first();

    if (!user) {
      return res
        .status(401)
        .json({ success: false, message: 'Credenciais inválidas' });
    }

    const isValidPassword = await bcrypt.compare(password, user.password);
    if (!isValidPassword) {
      return res
        .status(401)
        .json({ success: false, message: 'Credenciais inválidas' });
    }

    const token = jwt.sign(
      { id: user.id, username: user.username },
      process.env.JWT_SECRET,
      { expiresIn: '24h' }
    );

    return res.json({ success: true, data: { token } });
  } catch (err) {
    console.error('Erro ao autenticar usuário:', err);
    return res
      .status(500)
      .json({ success: false, message: 'Erro interno do servidor' });
  }
});

// POST /api/users/register
router.post('/register', authLimiter, async (req, res) => {
  const { username, password } = req.body;

  if (
    !username ||
    !password ||
    typeof username !== 'string' ||
    typeof password !== 'string' ||
    username.length < 3 ||
    password.length < 4
  ) {
    return res.status(400).json({
      success: false,
      message:
        'Nome de usuário deve ter pelo menos 3 caracteres e senha pelo menos 4 caracteres',
    });
  }

  try {
    const existing = await db('users').where({ username }).first();
    if (existing) {
      return res
        .status(409)
        .json({ success: false, message: 'Nome de usuário já está em uso' });
    }

    const hashedPassword = await bcrypt.hash(password, 10);

    const [row] = await db('users')
      .insert({ username, password: hashedPassword })
      .returning('id');

    const token = jwt.sign(
      { id: row.id, username },
      process.env.JWT_SECRET,
      { expiresIn: '24h' }
    );

    return res.status(201).json({ success: true, data: { token } });
  } catch (err) {
    console.error('Erro ao registrar usuário:', err);
    return res.status(500).json({
      success: false,
      message: 'Erro interno ao registrar usuário',
    });
  }
});

module.exports = router;

// === Fim de: ./routes/users.js ===


// === Início de: ./routes/chargers.js ===
// routes/chargers.js
const express = require('express');
const { body, param, validationResult } = require('express-validator');
const router = express.Router();
const verifyToken = require('../middleware/verifyToken');
const db = require('../db');
const crypto = require('crypto');
const axios = require('axios');
const mqttClient = require('../mqtt/client');

router.use(verifyToken);

function handleValidation(req, res, next) {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ success: false, errors: errors.array() });
  }
  next();
}

// throttling simples em memória (por charger)
const lastConfigUpdateAt = new Map();

// GET /api/chargers
router.get('/', async (req, res) => {
  const userId = req.user.id;
  try {
    const chargers = await db('chargers')
      .where({ user_id: userId })
      .select('*');
    res.json({ success: true, data: chargers });
  } catch (err) {
    console.error('Erro ao buscar carregadores:', err);
    res.status(500).json({ success: false, message: 'Erro no servidor' });
  }
});

// GET /api/chargers/:id
router.get(
  '/:id',
  [param('id').isUUID()],
  handleValidation,
  async (req, res) => {
    const { id } = req.params;
    const userId = req.user.id;

    try {
      const charger = await db('chargers')
        .where({ id, user_id: userId })
        .first();

      if (!charger) {
        return res
          .status(404)
          .json({ success: false, message: 'Carregador não encontrado' });
      }

      let config = null;
      try {
        config = await db('charger_configs')
          .where({ charger_id: charger.id })
          .first();
      } catch (cfgErr) {
        console.error('[GET charger] erro ao buscar charger_configs:', cfgErr);
      }

      if (!config)
        config = {
          charger_id: charger.id,
          max_charging_current: 32,
          require_auth: false,
          rcm_enabled: false,
          temperature_limit: 60,
        };

      const numericFields = [
        'power_l1',
        'power_l2',
        'power_l3',
        'voltage_l1',
        'voltage_l2',
        'voltage_l3',
        'current_l1',
        'current_l2',
        'current_l3',
        'charging_current',
        'consumption',
      ];

      numericFields.forEach((field) => {
        const v = charger[field];
        charger[field] =
          v === null || v === undefined || v === '' ? 0 : Number(v);
        if (Number.isNaN(charger[field])) charger[field] = 0;
      });

      return res.json({ success: true, data: { ...charger, config } });
    } catch (err) {
      console.error('Erro ao buscar carregador:', err);

      return res.status(500).json({
        success: false,
        message:
          process.env.NODE_ENV === 'development'
            ? err.message || 'Erro no servidor'
            : 'Erro no servidor',
      });
    }
  }
);

// POST /api/chargers
router.post(
  '/',
  [
    body('location')
      .exists()
      .withMessage('O campo location é obrigatório')
      .isString()
      .withMessage('Location deve ser uma string')
      .isLength({ min: 1 })
      .withMessage('Location não pode estar vazio')
      .trim(),
  ],
  handleValidation,
  async (req, res) => {
    const { location } = req.body;
    const userId = req.user.id;
    const now = new Date().toISOString();

    let chargerID;
    do {
      chargerID = crypto.randomBytes(6).toString('hex');
    } while (
      await db('chargers').where({ mqtt_topic: chargerID }).first()
    );

    const mqtt_topic = chargerID;
    const mqtt_user = chargerID;
    const mqtt_pass = crypto.randomBytes(6).toString('hex');

    try {
      const [charger] = await db('chargers')
        .insert({
          user_id: userId,
          location,
          status: 'offline',
          charging_current: 0,
          charging_time: 0,
          consumption: 0,
          power_l1: 0.0,
          power_l2: 0.0,
          power_l3: 0.0,
          voltage_l1: 0.0,
          voltage_l2: 0.0,
          voltage_l3: 0.0,
          current_l1: 0.0,
          current_l2: 0.0,
          current_l3: 0.0,
          mqtt_user,
          mqtt_pass,
          mqtt_topic,
          updated_at: now,
        })
        .returning('*');

      await db('charger_configs').insert({
        charger_id: charger.id,
        max_charging_current: 32,
        require_auth: false,
        rcm_enabled: false,
        temperature_limit: 60,
        config_received_at: now,
      });

      try {
        await axios.post(
          'http://192.168.1.110:7000/client/create',
          {
            client_name: charger.mqtt_user,
            chargeID: charger.mqtt_topic,
            password: charger.mqtt_pass,
          },
          { timeout: 5000 }
        );
      } catch (err) {
        console.error(
          'Erro ao criar cliente Mosquitto:',
          err?.response?.data || err.message
        );
      }

      res.status(201).json({ success: true, data: charger });
    } catch (err) {
      console.error('Erro ao criar carregador:', err);
      res.status(500).json({ success: false, message: 'Erro no servidor' });
    }
  }
);

// PUT /api/chargers/:id
router.put(
  '/:id',
  [
    param('id').isUUID(),
    body('charger').optional().isObject(),
    body('config').optional().isObject(),
    body('location').optional().isString(), // compat front
  ],
  handleValidation,
  async (req, res) => {
    const { id } = req.params;
    const userId = req.user.id;

    let { charger = {}, config = {} } = req.body;

    // ✅ compatibilidade com front antigo: { location }
    if (req.body.location && !charger.location) {
      charger.location = req.body.location;
    }

    const trx = await db.transaction();

    const ALLOWED_CHARGER_FIELDS = ['location'];
    const safeChargerUpdate = Object.fromEntries(
      Object.entries(charger).filter(([k]) =>
        ALLOWED_CHARGER_FIELDS.includes(k)
      )
    );

    try {
      let updatedCharger;

      if (Object.keys(safeChargerUpdate).length > 0) {
        [updatedCharger] = await trx('chargers')
          .where({ id, user_id: userId })
          .update({
            ...safeChargerUpdate,
            updated_at: new Date().toISOString(),
          })
          .returning('*');
      } else {
        updatedCharger = await trx('chargers')
          .where({ id, user_id: userId })
          .first();
      }

      if (!updatedCharger) {
        await trx.rollback();
        return res
          .status(404)
          .json({ success: false, message: 'Carregador não encontrado' });
      }

      if (Object.keys(config).length > 0) {
        const existing = await trx('charger_configs')
          .where({ charger_id: id })
          .first();

        const data = {
          ...config,
          config_received_at: new Date().toISOString(),
        };

        if (existing) {
          await trx('charger_configs')
            .where({ charger_id: id })
            .update(data);
        } else {
          await trx('charger_configs')
            .insert({ charger_id: id, ...data });
        }
      }

      await trx.commit();
      res.json({ success: true, data: updatedCharger });
    } catch (err) {
      await trx.rollback();
      console.error('Erro ao atualizar carregador:', err);
      res.status(500).json({ success: false, message: 'Erro no servidor' });
    }
  }
);

// DELETE /api/chargers/:id
router.delete(
  '/:id',
  [param('id').isUUID()],
  handleValidation,
  async (req, res) => {
    const { id } = req.params;
    const userId = req.user.id;

    try {
      const charger = await db('chargers')
        .where({ id, user_id: userId })
        .first();

      if (!charger) {
        return res
          .status(404)
          .json({ success: false, message: 'Carregador não encontrado' });
      }

      try {
        await axios.post(
          'http://192.168.1.110:7000/client/delete',
          {
            client_name: charger.mqtt_user,
            chargeID: charger.mqtt_topic,
          },
          { timeout: 5000 }
        );
      } catch (err) {
        console.error(
          'Erro ao deletar cliente Mosquitto:',
          err?.response?.data || err.message
        );
      }

      await db('chargers').where({ id }).del();
      res.json({ success: true, message: 'Carregador excluído com sucesso' });
    } catch (err) {
      console.error('Erro ao excluir carregador:', err);
      res.status(500).json({ success: false, message: 'Erro no servidor' });
    }
  }
);

// PUT /api/chargers/:id/config
router.put(
  '/:id/config',
  [param('id').isUUID(), body('config').isObject()],
  handleValidation,
  async (req, res) => {
    const { id } = req.params;
    const { config } = req.body;
    const userId = req.user.id;

    try {
      const charger = await db('chargers')
        .where({ id, user_id: userId })
        .first();

      if (!charger) {
        return res.status(404).json({
          success: false,
          message: 'Charger not found or unauthorized',
        });
      }

      // lê config atual
      const existing = await db('charger_configs')
        .where({ charger_id: id })
        .first();

      // throttle simples por charger
      const nowMs = Date.now();
      const lastMs = lastConfigUpdateAt.get(id) || 0;
      const tooSoon = nowMs - lastMs < 800;

      // se veio max_charging_current, normaliza/clampa
      let safeConfig = { ...config };
      if (safeConfig.max_charging_current !== undefined) {
        const safeAmp = Math.max(6, Math.min(Number(safeConfig.max_charging_current), 64));
        safeConfig.max_charging_current = safeAmp;
      }

      const onlyAmp =
        Object.keys(safeConfig).length === 1 &&
        safeConfig.max_charging_current !== undefined;

      // dedupe: mesmo valor -> responde ok sem update nem mqtt
      if (
        existing &&
        onlyAmp &&
        existing.max_charging_current === safeConfig.max_charging_current
      ) {
        return res.json({ success: true, data: existing, message: 'Config unchanged' });
      }

      // se é update muito rápido e igual ao que já está, ignora
      if (tooSoon && existing && onlyAmp) {
        return res.json({ success: true, data: existing, message: 'Throttled' });
      }

      // ✅ upsert
      let updatedConfig;
      if (existing) {
        [updatedConfig] = await db('charger_configs')
          .where({ charger_id: id })
          .update({
            ...safeConfig,
            config_received_at: new Date().toISOString(),
          })
          .returning('*');
      } else {
        [updatedConfig] = await db('charger_configs')
          .insert({
            charger_id: id,
            ...safeConfig,
            config_received_at: new Date().toISOString(),
          })
          .returning('*');
      }

      lastConfigUpdateAt.set(id, nowMs);

      const keyMap = {
        max_charging_current: 'maxChargingCurrent',
        require_auth: 'requireAuth',
        temperature_limit: 'temperatureThreshold',
      };

      Object.entries(safeConfig).forEach(([dbKey, value]) => {
        const publishKey = keyMap[dbKey] || dbKey;
        mqttClient.sendConfig(charger.mqtt_topic, publishKey, value);
      });

      return res.json({ success: true, data: updatedConfig });
    } catch (err) {
      console.error('Error updating config:', err);
      return res
        .status(500)
        .json({ success: false, message: 'Server error' });
    }
  }
);

// GET /api/chargers/:id/schedule
router.get(
  '/:id/schedule',
  [param('id').isUUID()],
  handleValidation,
  async (req, res) => {
    const { id } = req.params;
    const userId = req.user.id;

    const charger = await db('chargers')
      .where({ id, user_id: userId })
      .first();

    if (!charger) {
      return res
        .status(404)
        .json({ success: false, message: 'Carregador não encontrado' });
    }

    try {
      const schedules = await db('charger_schedules')
        .where({ charger_id: id })
        .orderBy('created_at', 'desc');

      return res.json({ success: true, data: schedules });
    } catch (err) {
      console.error('Erro ao buscar schedules:', err);
      return res.json({ success: true, data: [] });
    }
  }
);

// POST /api/chargers/:id/schedule
router.post(
  '/:id/schedule',
  [
    param('id').isUUID(),
    body('start').matches(/^\d{2}:\d{2}$/).withMessage('start inválido'),
    body('end').matches(/^\d{2}:\d{2}$/).withMessage('end inválido'),
    body('repeat')
      .isIn(['everyday', 'weekdays', 'weekends'])
      .withMessage('repeat inválido'),
  ],
  handleValidation,
  async (req, res) => {
    const { id } = req.params;
    const { start, end, repeat } = req.body;
    const userId = req.user.id;

    const charger = await db('chargers')
      .where({ id, user_id: userId })
      .first();

    if (!charger) {
      return res
        .status(404)
        .json({ success: false, message: 'Carregador não encontrado' });
    }

    try {
      const [inserted] = await db('charger_schedules')
        .insert({
          charger_id: id,
          start,
          end,
          repeat,
          created_at: new Date().toISOString(),
        })
        .returning('*');

      return res.status(201).json({ success: true, data: inserted });
    } catch (err) {
      console.error('Erro ao criar schedule:', err);
      return res
        .status(500)
        .json({ success: false, message: 'Erro ao criar schedule' });
    }
  }
);

// POST /api/chargers/:id/action
router.post(
  '/:id/action',
  [
    param('id').isUUID().withMessage('ID do carregador inválido'),
    body('action').isIn(['start', 'stop']).withMessage('Ação inválida'),
    body('ampLimit')
      .optional()
      .isInt({ min: 6, max: 64 })
      .withMessage('ampLimit deve estar entre 6 e 64'),
  ],
  handleValidation,
  async (req, res) => {
    const { id } = req.params;
    const { action, ampLimit } = req.body;
    const userId = req.user.id;

    try {
      const charger = await db('chargers')
        .where({ id, user_id: userId })
        .first();

      if (!charger) {
        return res.status(404).json({
          success: false,
          message: 'Carregador não encontrado ou não autorizado',
        });
      }

      if (ampLimit !== undefined) {
        const safeAmp = Math.max(6, Math.min(Number(ampLimit), 64));

        await db('charger_configs')
          .where({ charger_id: id })
          .update({
            max_charging_current: safeAmp,
            config_received_at: new Date().toISOString(),
          });

        mqttClient.sendConfig(
          charger.mqtt_topic,
          'maxChargingCurrent',
          safeAmp
        );
      }

      const enable = action === 'start';
      mqttClient.sendEnable(charger.mqtt_topic, enable);

      return res.json({
        success: true,
        message: `Comando '${action}' enviado com sucesso`,
      });
    } catch (err) {
      console.error(
        `Erro ao processar ação '${action}' para carregador ${id}:`,
        err
      );
      return res
        .status(500)
        .json({ success: false, message: 'Erro ao processar ação' });
    }
  }
);

module.exports = router;

// === Fim de: ./routes/chargers.js ===


// === Início de: ./middleware/verifyToken.js ===
const jwt = require('jsonwebtoken');

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

function verifyToken(req, res, next) {
  const authHeader =
    req.headers['authorization'] || req.headers['Authorization'];

  if (!authHeader) {
    return res.status(403).json({ error: 'Token não fornecido' });
  }

  const match = authHeader.match(/^Bearer\s+(.+)$/i);
  if (!match) {
    return res
      .status(403)
      .json({ error: 'Token malformado. Use "Bearer <token>"' });
  }

  const token = match[1];

  jwt.verify(token, process.env.JWT_SECRET, (err, payload) => {
    if (err) {
      if (err.name === 'TokenExpiredError') {
        return res.status(403).json({ error: 'Sessão expirada' });
      }
      return res.status(403).json({ error: 'Token inválido' });
    }

    if (!payload?.id) {
      return res.status(403).json({ error: 'Token inválido' });
    }

    req.user = payload;
    next();
  });
}

module.exports = verifyToken;

// === Fim de: ./middleware/verifyToken.js ===
