const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion, downloadMediaMessage } = require('@whiskeysockets/baileys');
const express = require('express');
const QRCode = require('qrcode');
const pino = require('pino');
const fs = require('fs');
const path = require('path');

const app = express();
app.use(express.json());

// Configurable via environment variables
const PORT = process.env.PORT || 21465;
const API_KEY = process.env.API_KEY || 'nexus-wpp-secret-2026';
const TENANT_ID = process.env.TENANT_ID || '';
const WEBHOOK_BASE = process.env.WEBHOOK_BASE || 'http://localhost:5001/pos/api/whatsapp/webhook';
const WEBHOOK_URL = TENANT_ID ? `${WEBHOOK_BASE}?tenant_id=${TENANT_ID}` : WEBHOOK_BASE;
const AUTH_DIR = process.env.AUTH_DIR || '/app/auth';

let sock = null;
let qrCode = null;
let connectionState = 'disconnected';
let retry440Count = 0;
let lastConnectAttempt = 0;
const logger = pino({ level: process.env.LOG_LEVEL || 'warn' });

// Queue for outgoing messages when disconnected
const sendQueue = [];
let queueTimer = null;
let connectWatchdog = null;
let staleWatchdog = null;
let queueFlushInterval = null;
const WATCHDOG_MS = 300000;   // 5 minutos para dar tiempo al QR scanning
const STALE_MS = 1800000;     // 30 minutos (keepalive ya maneja pings, no forzamos reconexión por inactividad)
let lastActivity = Date.now();

function updateActivity() {
    lastActivity = Date.now();
}

function clearAuthState() {
    try {
        if (fs.existsSync(AUTH_DIR)) {
            fs.readdirSync(AUTH_DIR).forEach(f => {
                try { fs.unlinkSync(path.join(AUTH_DIR, f)); } catch (e) {}
            });
        }
        console.log(`[Tenant ${TENANT_ID}] Auth state cleared`);
    } catch (e) {
        console.error(`[Tenant ${TENANT_ID}] Failed to clear auth:`, e.message);
    }
}

function flushSendQueue() {
    if (!sock || connectionState !== 'open') return;
    while (sendQueue.length > 0) {
        const item = sendQueue.shift();
        try {
            sock.sendMessage(item.jid, { text: item.message });
            console.log(`[Tenant ${TENANT_ID}] Flushed queued message to ${item.jid}`);
        } catch (e) {
            console.error(`[Tenant ${TENANT_ID}] Failed to flush queued message:`, e.message);
            sendQueue.unshift(item);
            break;
        }
    }
}

function clearWatchdog() {
    if (connectWatchdog) { clearTimeout(connectWatchdog); connectWatchdog = null; }
    if (staleWatchdog) { clearInterval(staleWatchdog); staleWatchdog = null; }
    if (queueFlushInterval) { clearInterval(queueFlushInterval); queueFlushInterval = null; }
}

function scheduleStaleWatchdog() {
    // ELIMINADO: el stale watchdog forzaba reconexión cada 90s sin mensajes,
    // lo cual destruía la sesión de Baileys y provocaba 440 + limpieza de auth.
    // Baileys ya envía keepalive cada 15s (keepAliveIntervalMs). No es necesario.
    if (staleWatchdog) { clearInterval(staleWatchdog); staleWatchdog = null; }
}

function scheduleWatchdog() {
    clearWatchdog();
    connectWatchdog = setTimeout(() => {
        if (connectionState !== 'open') {
            console.log(`[Tenant ${TENANT_ID}] Watchdog: connection not stable after ${WATCHDOG_MS/1000}s, forcing reconnect`);
            try { sock?.ws?.close(); } catch (e) {}
            sock = null;
            connectionState = 'disconnected';
            setTimeout(connectWhatsApp, 30000);
        }
    }, WATCHDOG_MS);
}

async function connectWhatsApp() {
    // Rate limit: max 1 attempt per 30 seconds to avoid WhatsApp throttling
    const now = Date.now();
    if (now - lastConnectAttempt < 30000) {
        const wait = 30000 - (now - lastConnectAttempt);
        console.log(`[Tenant ${TENANT_ID}] Rate limiting: waiting ${wait/1000}s before next attempt`);
        setTimeout(connectWhatsApp, wait);
        return;
    }
    lastConnectAttempt = now;

    clearWatchdog();
    const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR);
    const { version } = await fetchLatestBaileysVersion();
    console.log(`[Tenant ${TENANT_ID}] Connecting with Baileys v` + version.join('.'));
    connectionState = 'connecting';
    scheduleWatchdog();

    sock = makeWASocket({
        version,
        auth: state,
        logger,
        // Use a generic Ubuntu + Chrome fingerprint — less suspicious than raw Linux
        browser: ['Chrome', 'Windows', '124.0.0.0'],
        defaultQueryTimeoutMs: 60000,
        keepAliveIntervalMs: 15000,
        markOnlineOnConnect: false,
        syncFullHistory: false,
        shouldSyncHistoryMessage: () => false,
        shouldIgnoreJid: (jid) => false,
        retryRequestDelayMs: 250,
        maxMsgRetryCount: 2,
        connectTimeoutMs: 60000,
        emitOwnEvents: false,
    });
    sock.ev.on('creds.update', saveCreds);

    sock.ev.on('connection.update', async (update) => {
        const { connection, lastDisconnect, qr } = update;
        if (qr) {
            qrCode = await QRCode.toDataURL(qr);
            connectionState = 'qr';
            console.log(`[Tenant ${TENANT_ID}] QR code generated!`);
        }
        if (connection === 'close') {
            clearWatchdog();
            connectionState = 'disconnected';
            qrCode = null;
            const statusCode = lastDisconnect?.error?.output?.statusCode;
            // Baileys wraps some errors differently; try both paths
            const reason = statusCode || lastDisconnect?.error?.statusCode;
            console.log(`[Tenant ${TENANT_ID}] Disconnected, reason:`, reason);

            if (reason === DisconnectReason.loggedOut) {
                console.log(`[Tenant ${TENANT_ID}] Logged out — clearing auth`);
                clearAuthState();
                sock = null;
                retry440Count = 0;
                return;
            }

            if (reason === 440) {
                // 440 = conflict/replaced. WhatsApp reemplazó esta sesión (puede ser
                // por reconexión muy rápida, o porque el teléfono abrió otra sesión).
                // NUNCA limpiamos auth automáticamente — solo esperamos más tiempo.
                retry440Count++;
                const delay = retry440Count >= 3 ? 600000 : 120000; // 2min → 10min
                console.log(`[Tenant ${TENANT_ID}] 440 Session replaced — reconnecting in ${delay/1000}s with existing creds (attempt ${retry440Count})`);
                sock = null;
                setTimeout(connectWhatsApp, delay);
                return;
            }

            if (reason === 515) {
                // 515 = stream error / restart required. WhatsApp sends this after
                // successful pairing to force a reconnect with the new credentials.
                // DO NOT clear auth — the credentials were just saved by creds.update.
                console.log(`[Tenant ${TENANT_ID}] 515 Restart required — reconnecting in 5s with saved creds`);
                sock = null;
                setTimeout(connectWhatsApp, 5000);
                return;
            }

            if (reason === 428) {
                console.log(`[Tenant ${TENANT_ID}] 428 Server terminated — waiting 60s`);
                sock = null;
                setTimeout(connectWhatsApp, 60000);
                return;
            }

            if (reason === 408) {
                // 408 during init queries = rate-limit o auth parcialmente inválido.
                // No limpiamos auth automáticamente; esperamos más tiempo.
                console.log(`[Tenant ${TENANT_ID}] 408 Timeout — waiting 120s`);
                sock = null;
                setTimeout(connectWhatsApp, 120000);
                return;
            }

            // Any other error — wait 30s and retry
            console.log(`[Tenant ${TENANT_ID}] Reconnecting in 30s... (reason: ${reason})`);
            sock = null;
            setTimeout(connectWhatsApp, 30000);
        }

        if (connection === 'open') {
            // Race-condition guard: if sock was nulled by a concurrent disconnect,
            // ignore this stale 'open' event.
            if (!sock) {
                console.log(`[Tenant ${TENANT_ID}] Ignoring stale 'open' event (sock is null)`);
                return;
            }
            clearWatchdog();
            connectionState = 'open';
            qrCode = null;
            retry440Count = 0;
            updateActivity();
            // Stale watchdog eliminado — Baileys ya mantiene keepalive.
            console.log(`[Tenant ${TENANT_ID}] Connected!`);
            flushSendQueue();
            if (!queueFlushInterval) {
                queueFlushInterval = setInterval(() => {
                    if (connectionState === 'open') flushSendQueue();
                }, 5000);
            }
        }
    });

    sock.ev.on('messages.upsert', async ({ messages }) => {
        for (const msg of messages) {
            if (msg.key.fromMe) continue;
            // Skip system/receipt messages with no meaningful content
            if (!msg.message || Object.keys(msg.message).length === 0) continue;
            const phone = msg.key.remoteJid.replace('@s.whatsapp.net', '');
            const message = msg.message || {};
            
            let media_kind = 'text';
            let media_base64 = null;
            let media_mimetype = null;
            let media_caption = null;
            let media_ptt = false;
            let latitude = null;
            let longitude = null;
            let text = '';

            if (message.conversation) {
                text = message.conversation;
            } else if (message.extendedTextMessage) {
                text = message.extendedTextMessage.text || '';
            } else if (message.imageMessage) {
                media_kind = 'image';
                media_mimetype = message.imageMessage.mimetype;
                media_caption = message.imageMessage.caption || '';
                text = media_caption;
            } else if (message.videoMessage) {
                media_kind = 'video';
                media_mimetype = message.videoMessage.mimetype;
                media_caption = message.videoMessage.caption || '';
                text = media_caption;
            } else if (message.audioMessage) {
                media_kind = 'audio';
                media_mimetype = message.audioMessage.mimetype;
                media_ptt = message.audioMessage.ptt || false;
            } else if (message.locationMessage) {
                media_kind = 'location';
                latitude = message.locationMessage.degreesLatitude;
                longitude = message.locationMessage.degreesLongitude;
                text = `Ubicación: ${latitude}, ${longitude}`;
            }

            if (['image', 'video', 'audio'].includes(media_kind)) {
                try {
                    const buffer = await downloadMediaMessage(msg, 'buffer', {}, { logger: pino({ level: 'silent' }) });
                    if (buffer) media_base64 = buffer.toString('base64');
                } catch (e) {
                    console.log(`[Tenant ${TENANT_ID}] Media download failed:`, e.message);
                }
            }

            updateActivity();
            console.log(`[Tenant ${TENANT_ID}] From ${phone}: [${media_kind}] ${text.substring(0, 80)}`);
            try {
                const controller = new AbortController();
                const timeoutId = setTimeout(() => controller.abort(), 30000);
                const resp = await fetch(WEBHOOK_URL, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        event: 'messages.upsert',
                        data: {
                            key: msg.key,
                            message: msg.message,
                            messageTimestamp: msg.messageTimestamp,
                            media_kind,
                            media_base64,
                            media_mimetype,
                            media_caption,
                            media_ptt,
                            latitude,
                            longitude,
                            push_name: msg.pushName || '',
                            sender_pn: msg.key?.senderPn || ''
                        }
                    }),
                    signal: controller.signal
                });
                clearTimeout(timeoutId);
                if (resp.ok) {
                    console.log(`[Tenant ${TENANT_ID}] Webhook delivered OK (${resp.status})`);
                } else {
                    console.log(`[Tenant ${TENANT_ID}] Webhook returned ${resp.status}`);
                }
            } catch (e) {
                console.log(`[Tenant ${TENANT_ID}] Webhook failed:`, e.message);
            }
        }
    });
}

app.get('/', (req, res) => res.json({ status: 'ok', service: 'Nexus WhatsApp Bridge', tenant: TENANT_ID, state: connectionState, queued: sendQueue.length }));
app.get('/status', (req, res) => res.json({ state: connectionState, hasQr: !!qrCode }));
app.get('/qr', (req, res) => {
    if (connectionState === 'open') return res.json({ state: 'open', message: 'Already connected' });
    if (!qrCode) return res.json({ state: connectionState, qr: null, message: 'QR not ready' });
    res.json({ state: 'qr', qr: qrCode });
});
app.post('/connect', async (req, res) => {
    if (sock) {
        try { await sock.logout(); } catch (e) {}
        sock = null;
    }
    retry440Count = 0;
    clearAuthState();
    qrCode = null;
    connectionState = 'connecting';
    connectWhatsApp();
    res.json({ state: connectionState });
});
app.post('/send', async (req, res) => {
    const { phone, message } = req.body;
    if (!phone || !message) {
        return res.status(400).json({ error: 'phone and message required' });
    }
    const jid = phone.includes('@') ? phone : phone + '@s.whatsapp.net';
    console.log(`[Tenant ${TENANT_ID}] /send called for ${jid}. state=${connectionState}, sock=${!!sock}`);

    if (connectionState === 'open' && sock) {
        try {
            const r = await sock.sendMessage(jid, { text: message });
            res.json({ success: true, id: r.key.id });
            return;
        } catch (e) {
            console.log(`[Tenant ${TENANT_ID}] Send failed, will queue:`, e.message);
            // fall through to queue
        }
    }

    sendQueue.push({ jid, message });
    console.log(`[Tenant ${TENANT_ID}] Message queued for ${jid} (state: ${connectionState}, queue size: ${sendQueue.length})`);
    res.status(202).json({ queued: true, state: connectionState });
});
app.post('/send-image', async (req, res) => {
    const { phone, caption, base64 } = req.body;
    if (!phone || !base64) {
        return res.status(400).json({ error: 'phone and base64 required' });
    }
    const jid = phone.includes('@') ? phone : phone + '@s.whatsapp.net';

    if (connectionState !== 'open' || !sock) {
        return res.status(400).json({ error: 'Not connected' });
    }

    try {
        const buffer = Buffer.from(base64, 'base64');
        const r = await sock.sendMessage(jid, {
            image: buffer,
            caption: caption || ''
        });
        res.json({ success: true, id: r.key.id });
    } catch (e) {
        console.log(`[Tenant ${TENANT_ID}] Send image failed:`, e.message);
        res.status(500).json({ error: e.message });
    }
});

app.post('/logout', async (req, res) => {
    if (sock) { await sock.logout(); sock = null; }
    clearAuthState();
    qrCode = null;
    connectionState = 'disconnected';
    retry440Count = 0;
    res.json({ state: 'disconnected' });
});

app.listen(PORT, () => {
    console.log(`[Tenant ${TENANT_ID}] WhatsApp Bridge on port ${PORT}`);
    connectWhatsApp();
});
