// src/services/chargers.service.js
const crypto = require('crypto');
const axios = require('axios');

const chargersRepo = require('../repositories/chargers.repo');
const { httpError } = require('../utils/httpError');
const mqttClient = require('../mqtt');
const config = require('../config');

function stripUndef(obj) {
    return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
}

// throttle in-memory (por charger)
const lastConfigUpdateAt = new Map();

function clampAmp(v) {
    const n = Number(v);
    return Math.max(6, Math.min(n, 64));
}

function clampTemp(v) {
    const n = Number(v);
    return Math.max(0, Math.min(n, 120));
}

function normalizeNumericFields(charger) {
    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 charger;
}

function mosquittoUrl(path) {
    const base = config.mosquittoMgmt.baseUrl;
    if (!base) return '';
    return `${base.replace(/\/+$/, '')}${path.startsWith('/') ? '' : '/'}${path}`;
}

async function mosquittoCreateClient(charger) {
    const url = mosquittoUrl('/client/create');
    if (!url) {
        console.warn('[MosquittoMgmt] MOSQUITTO_MGMT_URL não definido. Skip create.');
        return;
    }

    try {
        await axios.post(
            url,
            {
                client_name: charger.mqtt_user,
                chargeID: charger.mqtt_topic,
                password: charger.mqtt_pass,
            },
            { timeout: config.mosquittoMgmt.timeoutMs }
        );
    } catch (err) {
        console.error('[MosquittoMgmt] Erro ao criar cliente:', err?.response?.data || err.message);
    }
}

async function mosquittoDeleteClient(charger) {
    const url = mosquittoUrl('/client/delete');
    if (!url) {
        console.warn('[MosquittoMgmt] MOSQUITTO_MGMT_URL não definido. Skip delete.');
        return;
    }

    try {
        await axios.post(
            url,
            {
                client_name: charger.mqtt_user,
                chargeID: charger.mqtt_topic,
            },
            { timeout: config.mosquittoMgmt.timeoutMs }
        );
    } catch (err) {
        console.error('[MosquittoMgmt] Erro ao deletar cliente:', err?.response?.data || err.message);
    }
}

async function list(userId) {
    return chargersRepo.listByUser(userId);
}

async function getOne(userId, id) {
    const charger = await chargersRepo.findByIdForUser(id, userId);
    if (!charger) throw httpError(404, 'Carregador não encontrado');

    let cfg = await chargersRepo.getConfig(charger.id);
    if (!cfg) {
        cfg = {
            charger_id: charger.id,
            max_charging_current: 32,
            require_auth: false,
            rcm_enabled: false,
            temperature_limit: 60,
        };
    }

    return { ...normalizeNumericFields(charger), config: cfg };
}

async function create(userId, location) {
    if (!location || typeof location !== 'string' || location.trim().length < 1) {
        throw httpError(400, 'O campo location é obrigatório');
    }

    const now = new Date().toISOString();

    let chargerID;
    do {
        chargerID = crypto.randomBytes(6).toString('hex');
    } while (await chargersRepo.findByMqttTopic(chargerID));

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

    const charger = await chargersRepo.insertCharger({
        user_id: userId,
        location: location.trim(),
        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,
    });

    await chargersRepo.insertConfig({
        charger_id: charger.id,
        max_charging_current: 32,
        require_auth: false,
        rcm_enabled: false,
        temperature_limit: 60,
        config_received_at: now,
    });

    await mosquittoCreateClient(charger);

    return charger;
}

async function update(userId, id, payload = {}) {
    let { charger = {}, config: cfgPatch = {} } = payload;

    if (payload.location && !charger.location) charger.location = payload.location;

    const safeChargerUpdate = {};
    if (charger.location !== undefined) safeChargerUpdate.location = charger.location;

    let updatedCharger = null;

    if (Object.keys(safeChargerUpdate).length > 0) {
        updatedCharger = await chargersRepo.updateChargerForUser(id, userId, {
            ...safeChargerUpdate,
            updated_at: new Date().toISOString(),
        });
    } else {
        updatedCharger = await chargersRepo.findByIdForUser(id, userId);
    }

    if (!updatedCharger) throw httpError(404, 'Carregador não encontrado');

    // config patch com whitelist
    if (cfgPatch && Object.keys(cfgPatch).length > 0) {
        const ALLOWED = ['max_charging_current', 'require_auth', 'rcm_enabled', 'temperature_limit'];
        let safeConfig = Object.fromEntries(
            Object.entries(cfgPatch || {}).filter(([k]) => ALLOWED.includes(k))
        );

        if (safeConfig.max_charging_current !== undefined) {
            safeConfig.max_charging_current = clampAmp(safeConfig.max_charging_current);
        }
        if (safeConfig.temperature_limit !== undefined) {
            safeConfig.temperature_limit = clampTemp(safeConfig.temperature_limit);
        }

        await chargersRepo.upsertConfig(id, {
            ...stripUndef(safeConfig),
            config_received_at: new Date().toISOString(),
        });
    }

    return updatedCharger;
}

async function remove(userId, id) {
    const charger = await chargersRepo.findByIdForUser(id, userId);
    if (!charger) throw httpError(404, 'Carregador não encontrado');

    await mosquittoDeleteClient(charger);

    await chargersRepo.deleteChargerForUser(id, userId);
    return true;
}

async function updateConfig(userId, id, incomingConfig = {}) {
    const charger = await chargersRepo.findByIdForUser(id, userId);
    if (!charger) throw httpError(404, 'Charger not found or unauthorized');

    const existing = await chargersRepo.getConfig(id);

    const nowMs = Date.now();
    const lastMs = lastConfigUpdateAt.get(id) || 0;
    const tooSoon = nowMs - lastMs < 800;

    const ALLOWED = ['max_charging_current', 'require_auth', 'rcm_enabled', 'temperature_limit'];
    let safeConfig = Object.fromEntries(
        Object.entries(incomingConfig || {}).filter(([k]) => ALLOWED.includes(k))
    );

    if (safeConfig.max_charging_current !== undefined) {
        safeConfig.max_charging_current = clampAmp(safeConfig.max_charging_current);
    }
    if (safeConfig.temperature_limit !== undefined) {
        safeConfig.temperature_limit = clampTemp(safeConfig.temperature_limit);
    }

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

    if (
        existing &&
        onlyAmp &&
        Number(existing.max_charging_current) === Number(safeConfig.max_charging_current)
    ) {
        return { data: existing, message: 'Config unchanged' };
    }

    if (tooSoon && existing && onlyAmp) {
        return { data: existing, message: 'Throttled' };
    }

    const updated = await chargersRepo.upsertConfig(id, {
        ...safeConfig,
        config_received_at: new Date().toISOString(),
    });

    lastConfigUpdateAt.set(id, nowMs);

    const evseSettings = {};
    if (safeConfig.max_charging_current !== undefined) {
        evseSettings.currentLimit = Number(safeConfig.max_charging_current);
    }
    if (safeConfig.temperature_limit !== undefined) {
        evseSettings.temperatureLimit = Number(safeConfig.temperature_limit);
    }
    if (Object.keys(evseSettings).length > 0) {
        mqttClient.sendEvseSettings(charger.mqtt_topic, evseSettings);
    }

    return { data: updated };
}

async function getSchedules(userId, id) {
    const charger = await chargersRepo.findByIdForUser(id, userId);
    if (!charger) throw httpError(404, 'Carregador não encontrado');
    return chargersRepo.listSchedules(id);
}

async function createSchedule(userId, id, start, end, repeat) {
    const charger = await chargersRepo.findByIdForUser(id, userId);
    if (!charger) throw httpError(404, 'Carregador não encontrado');

    const row = await chargersRepo.insertSchedule({
        charger_id: id,
        start,
        end,
        repeat,
        created_at: new Date().toISOString(),
    });

    return row;
}

async function action(userId, id, actionName, ampLimit) {
    const charger = await chargersRepo.findByIdForUser(id, userId);
    if (!charger) throw httpError(404, 'Carregador não encontrado ou não autorizado');

    if (ampLimit !== undefined) {
        const safeAmp = clampAmp(ampLimit);

        await chargersRepo.upsertConfig(id, {
            max_charging_current: safeAmp,
            config_received_at: new Date().toISOString(),
        });

        mqttClient.sendEvseSettings(charger.mqtt_topic, { currentLimit: safeAmp });
    }

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

    return true;
}

module.exports = {
    list,
    getOne,
    create,
    update,
    remove,
    updateConfig,
    getSchedules,
    createSchedule,
    action,
};
