// src/mqtt/index.js
const mqtt = require('mqtt');
const EventEmitter = require('events');

const db = require('../db/knex');
const config = require('../config');
const { sendPushToUser } = require('../services/push.service');

const { createPublishers } = require('./publishers');
const { createMqttContext } = require('./context');

const {
  handleStateEvse,
  handleStateScheduler,
  handleStateLoadbalancing,
  handleStateAuth,
  handleStateMetersConfig,
} = require('./handlers/evse.handler');
const { handleStateMeter } = require('./handlers/meter.handler');
const { handleLegacyState, handleLegacyConfigResponse } = require('./handlers/legacy.handler');

const emitter = new EventEmitter();

// --------------------
// MQTT client
// --------------------
const MQTT_URL = config.mqtt.url;
const mqttUser = config.mqtt.user;
const mqttPass = config.mqtt.pass;

let connected = false;

const client = mqtt.connect(MQTT_URL, {
  username: mqttUser,
  password: mqttPass,
  reconnectPeriod: 2000,
});

client.on('connect', () => {
  connected = true;
});

client.on('offline', () => {
  connected = false;
});

client.on('close', () => {
  connected = false;
});

// publishers separados
const publishers = createPublishers(client);

// ctx separado (helpers + caches + db helpers)
const ctx = createMqttContext({ db, config, emitter, sendPushToUser });

// --------------------
// Subscribe
// --------------------
client.on('connect', () => {
  console.log('[MQTT] Conectado ao broker:', MQTT_URL);

  const fixedTopics = [
    '+/state/#',
    '+/state',
    '+/response/#',
    '+/response/config/evse',
  ];

  const envTopics = config.mqtt.subTopics || [];
  const topicsToSub = [...new Set([...fixedTopics, ...envTopics])];

  topicsToSub.forEach((t) => {
    client.subscribe(t, { qos: 0 }, (err, granted) => {
      if (err) {
        console.error('[MQTT] Falha ao subscrever', t, err.message);
      } else {
        console.log('[MQTT] Subscrito:', granted?.map((g) => g.topic).join(', ') || t);
      }
    });
  });
});

// --------------------
// Messages
// --------------------
client.on('message', async (topic, message) => {
  const parts = topic.split('/');
  const mqttTopic = parts[0];
  const subtopic = parts.slice(1).join('/');

  const payload = ctx.safeJsonParse(message);
  if (!payload) {
    console.warn('[MQTT] JSON inválido em', topic, 'payload=', message.toString());
    return;
  }

  try {
    if (subtopic === 'state/evse') return await handleStateEvse(ctx, mqttTopic, payload);
    if (subtopic === 'state/scheduler') return await handleStateScheduler(ctx, mqttTopic, payload);
    if (subtopic === 'state/loadbalancing')
      return await handleStateLoadbalancing(ctx, mqttTopic, payload);
    if (subtopic === 'state/auth') return await handleStateAuth(ctx, mqttTopic, payload);
    if (subtopic === 'state/meters-config')
      return await handleStateMetersConfig(ctx, mqttTopic, payload);
    if (subtopic === 'state/meter/evse')
      return await handleStateMeter(ctx, mqttTopic, payload, 'evse');
    if (subtopic === 'state/meter/grid')
      return await handleStateMeter(ctx, mqttTopic, payload, 'grid');

    if (subtopic === 'state') return await handleLegacyState(ctx, mqttTopic, payload);
    if (subtopic === 'response/config/evse')
      return await handleLegacyConfigResponse(ctx, mqttTopic, payload);
  } catch (err) {
    console.error('[MQTT] Erro ao processar', topic, err);
  }
});

// --------------------
// Broker offline / checker
// --------------------
client.on('offline', async () => {
  console.warn('[MQTT] Broker offline');

  try {
    const chargers = await ctx.db('chargers').select('id', 'user_id', 'location');
    const uniqueUsers = [...new Set(chargers.map((c) => c.user_id))];

    await Promise.allSettled(
      uniqueUsers.map((userId) =>
        ctx.sendPushToUser(userId, {
          title: '📡 Broker MQTT offline',
          body: 'O sistema perdeu ligação ao broker. Alguns estados podem estar desatualizados.',
          url: '/',
        })
      )
    );
  } catch (err) {
    console.error('[MQTT] erro offline push:', err.message);
  }
});

const offlineTimer = setInterval(async () => {
  try {
    const timeoutMinutes = config.chargerOfflineMinutes;
    const limitDate = new Date(Date.now() - timeoutMinutes * 60 * 1000);

    const offlineChargers = await ctx.db('chargers')
      .where('updated_at', '<', limitDate.toISOString())
      .andWhereNot({ status: 'offline' })
      .select('*');

    for (const ch of offlineChargers) {
      await ctx.db('chargers').where({ id: ch.id }).update({ status: 'offline' });

      ctx.lastDbStateByChargerId.delete(ch.id);
      ctx.lastMetaByChargerId.delete(ch.id);
      ctx.lastTotalEnergyByChargerId.delete(ch.id);
      ctx.sessionStartEnergyByChargerId.delete(ch.id);

      ctx.lastEnabled[ch.id] = false;

      await ctx.sendPushToUser(ch.user_id, {
        title: '🔌 Carregador offline',
        body: `${ch.location || 'Carregador'} está offline há mais de ${timeoutMinutes} min.`,
        url: `/charger/${ch.id}`,
      });
    }
  } catch (err) {
    console.error('[MQTT] offline checker erro:', err.message);
  }
}, 60 * 1000);

// --------------------
// API pública
// --------------------
function on(event, handler) {
  emitter.on(event, handler);
}

function isConnected() {
  return connected;
}

async function shutdown() {
  try {
    clearInterval(offlineTimer);
  } catch { }

  await new Promise((resolve) => {
    try {
      client.end(true, {}, resolve);
    } catch {
      resolve();
    }
  });
}

module.exports = {
  on,
  isConnected,
  shutdown,
  ...publishers,
};
