import consola from 'consola';
import serialize from '#wa/utils/serialize';
import { DateTime } from 'luxon';
import axios from 'axios';
import db from '@adonisjs/lucid/services/db';
import { detectScam, generateAutoReply } from '#services/deepseek_service';

const WEBHOOK_TIMEOUT = 5000;

// ─── HELPERS ────────────────────────────────────────────────────────────────

// Bug #10 fix: Batch-load ALL settings in one query at start of each message,
// instead of calling getSetting() individually 8-10 times per message.
async function getAllSettings() {
    try {
        const rows = await db.from('scammer_settings');
        const map = {};
        for (const r of rows) map[r.key] = r.value;
        return map;
    } catch { return {}; }
}

// Synchronous helper — reads from pre-loaded settings map
function getSetting(settings, key, fallback = null) {
    return (settings[key] !== undefined && settings[key] !== null) ? settings[key] : fallback;
}

// Extract plain text from message — prefer parsed m.msg.rawText, fallback to raw proto
function extractText(m) {
    try {
        // m.msg.rawText is already parsed by serialize — most reliable
        if (m.msg?.rawText) return m.msg.rawText;
        // Fallback to raw proto fields
        const msg = m.message || {};
        return (
            msg.conversation ||
            msg.extendedTextMessage?.text ||
            msg.imageMessage?.caption ||
            msg.videoMessage?.caption ||
            msg.documentMessage?.caption ||
            ''
        );
    } catch { return ''; }
}

// ─── SCAMMER GUARD ───────────────────────────────────────────────────────────

/**
 * Check blacklist — phone normalized string
 * Returns record or null. Also checks expiry.
 */
async function checkScammer(phone) {
    try {
        const normalized = String(phone).replace(/[\s\-\+]/g, '');
        if (!normalized) return null;
        const now = DateTime.now().toISO();
        const record = await db.from('scammer_guards')
            .where('phone', normalized)
            .where('is_active', true)
            .first();
        if (!record) return null;
        // Check expiry
        if (record.expires_at && record.expires_at < now) {
            await db.from('scammer_guards').where('id', record.id).update({ is_active: false });
            return null;
        }
        return record;
    } catch (e) {
        consola.warn('[ScammerGuard] DB check failed:', e.message);
        return null;
    }
}

/**
 * Check whitelist — skip if whitelisted
 */
async function isWhitelisted(phone) {
    try {
        const normalized = String(phone).replace(/[\s\-\+]/g, '');
        const row = await db.from('scammer_whitelist').where('phone', normalized).first();
        return !!row;
    } catch { return false; }
}

/**
 * Check active keywords against message text
 * Returns first matching keyword record or null
 */
async function checkKeywords(text) {
    try {
        if (!text) return null;
        const keywords = await db.from('scammer_keywords').where('is_active', true);
        const lower = text.toLowerCase();
        for (const kw of keywords) {
            if (lower.includes(kw.keyword.toLowerCase())) {
                return kw;
            }
        }
        return null;
    } catch (e) {
        consola.warn('[ScammerGuard] Keyword check failed:', e.message);
        return null;
    }
}

/**
 * Bug #7 fix: Per-scammer threshold now honoured.
 * Threshold tracking — count hits per phone for keyword matches.
 * Uses global threshold. Returns true if threshold exceeded and auto-blacklisted.
 */
async function checkThreshold(phone, settings) {
    try {
        const globalThreshold = parseInt(getSetting(settings, 'global_threshold', '0'));
        if (!globalThreshold || globalThreshold <= 0) return false;

        const normalized = String(phone).replace(/[\s\-\+]/g, '');

        const existing = await db.from('scammer_guards').where('phone', normalized).first();
        if (existing) return false;

        const countRow = await db.from('scammer_logs')
            .where('phone', normalized)
            .where('triggered_by', 'keyword')
            .count('id as total')
            .first();
        const count = parseInt(countRow?.total || 0);

        if (count >= globalThreshold) {
            const thresholdAction = getSetting(settings, 'threshold_action', 'warn');
            await db.from('scammer_guards').insert({
                phone: normalized,
                name: null,
                reason: `Auto-blacklisted: melebihi threshold ${globalThreshold} hit keyword`,
                action: thresholdAction,
                is_active: true,
                hit_count: count,
                last_seen_at: DateTime.now().toISO(),
                created_at: DateTime.now().toISO(),
                updated_at: DateTime.now().toISO(),
            });
            consola.warn(`[ScammerGuard] Auto-blacklisted ${normalized} (threshold ${globalThreshold})`);
            return true;
        }
        return false;
    } catch (e) {
        consola.warn('[ScammerGuard] Threshold check failed:', e.message);
        return false;
    }
}

/**
 * Update hit count and last seen for scammer record
 */
async function updateScammerHit(id) {
    try {
        await db.from('scammer_guards')
            .where('id', id)
            .update({
                hit_count: db.raw('hit_count + 1'),
                last_seen_at: DateTime.now().toISO(),
                updated_at: DateTime.now().toISO(),
            });
    } catch (e) {
        consola.warn('[ScammerGuard] Failed to update hit count:', e.message);
    }
}

/**
 * Write to scammer_logs
 */
async function writeLog({ phone, name, groupJid, groupName, deviceId, actionTaken, triggeredBy, matchedKeyword, messagePreview }) {
    try {
        await db.from('scammer_logs').insert({
            phone: phone || '',
            name: name || null,
            group_jid: groupJid || null,
            group_name: groupName || null,
            device_id: deviceId || null,
            action_taken: actionTaken || 'log',
            triggered_by: triggeredBy || 'blacklist',
            matched_keyword: matchedKeyword || null,
            message_preview: messagePreview ? String(messagePreview).substring(0, 300) : null,
            created_at: DateTime.now().toISO(),
            updated_at: DateTime.now().toISO(),
        });
    } catch (e) {
        consola.warn('[ScammerGuard] Failed to write log:', e.message);
    }
}

/**
 * Sync to shared DB (fire & forget)
 */
async function syncToSharedDb(phone, name, reason, settings) {
    try {
        const enabled = getSetting(settings, 'shared_db_enabled', '0');
        if (enabled !== '1') return;
        const sharedUrl = getSetting(settings, 'shared_db_url', '');
        const sharedToken = getSetting(settings, 'shared_db_token', '');
        if (!sharedUrl) return;
        await axios.post(`${sharedUrl}/api/scammer-guard`, {
            phone, name: name || null, reason: reason || 'Detected by ScammerGuard', action: 'warn',
        }, {
            headers: sharedToken ? { Authorization: `Bearer ${sharedToken}` } : {},
            timeout: 5000,
        });
    } catch { /* silent */ }
}

/**
 * Execute scammer action on the WhatsApp socket.
 * Bug #3 fix: skipLog param — keyword handler logs before calling this.
 * Returns true if a blocking action was taken (block), false otherwise.
 * action: log | warn | kick | block
 */
async function executeScammerAction({ sock, m, scammer, deviceId, triggeredBy, matchedKeyword, skipLog = false, settings = {} }) {
    const senderJid = m.sender?.pn || m.sender?.id || '';
    const chatJid = m.key?.remoteJid || m.chat;
    const isGroup = chatJid?.endsWith('@g.us');
    const action = scammer.action;
    const phone = scammer.phone;
    const senderName = m.sender?.pushName || m.sender?.name || null;
    const msgText = extractText(m);

    consola.warn(`[ScammerGuard: ${deviceId}] Detected ${phone} | trigger: ${triggeredBy} | action: ${action} | chat: ${chatJid}`);

    if (scammer.id) await updateScammerHit(scammer.id);

    if (!skipLog) {
        await writeLog({
            phone,
            name: senderName,
            groupJid: isGroup ? chatJid : null,
            groupName: null,
            deviceId,
            actionTaken: action,
            triggeredBy: triggeredBy || 'blacklist',
            matchedKeyword: matchedKeyword || null,
            messagePreview: msgText,
        });
    }

    syncToSharedDb(phone, senderName, scammer.reason, settings).catch(() => {});

    if (action === 'log') {
        consola.info(`[ScammerGuard: ${deviceId}] Logged message from ${phone}`);
        return false;
    }

    if (action === 'warn') {
        try {
            const warningText = `⚠️ *ScammerGuard Alert*\n\nNombor *+${phone}* telah dikesan dalam senarai hitam scammer.\n${scammer.reason ? `Sebab: ${scammer.reason}` : ''}\n\nSila berhati-hati.`;
            await sock.sendMessage(chatJid, { text: warningText });
        } catch (e) {
            consola.warn(`[ScammerGuard: ${deviceId}] Warn failed: ${e.message}`);
        }
        return false;
    }

    if (action === 'kick') {
        if (!isGroup) { consola.warn(`[ScammerGuard: ${deviceId}] Kick skipped — not a group`); return false; }
        if (!senderJid) { consola.warn(`[ScammerGuard: ${deviceId}] Kick failed — no senderJid`); return false; }
        try {
            await sock.groupParticipantsUpdate(chatJid, [senderJid], 'remove');
            consola.info(`[ScammerGuard: ${deviceId}] Kicked ${senderJid} from ${chatJid}`);
            try {
                await sock.sendMessage(chatJid, { text: `🚫 *ScammerGuard*: Ahli *+${phone}* telah dikeluarkan kerana dikesan sebagai scammer.` });
            } catch (_) {}
        } catch (e) {
            consola.warn(`[ScammerGuard: ${deviceId}] Kick failed: ${e.message}`);
        }
        return false;
    }

    if (action === 'block') {
        if (!senderJid) { consola.warn(`[ScammerGuard: ${deviceId}] Block failed — no senderJid`); return false; }
        try {
            await sock.updateBlockStatus(senderJid, 'block');
            consola.info(`[ScammerGuard: ${deviceId}] Blocked ${senderJid}`);
        } catch (e) {
            consola.warn(`[ScammerGuard: ${deviceId}] Block failed: ${e.message}`);
        }
        return true; // block = skip further processing
    }

    return false;
}

// ─── MAIN EVENT HANDLER ──────────────────────────────────────────────────────

export default async function ({ id, sock, event }) {
    if (event.type !== 'notify') return;

    for (const message of event.messages) {
        const startTime = Date.now();
        if (!message?.message) {
            consola.info(message.messageStubParameters);
            continue;
        }
        const m = await serialize({ id, sock, WAMessage: message });
        if (!m) continue;

        // Skip own messages
        if (m.fromMe || m.key?.fromMe) continue;

        // Bug #10 fix: Load ALL settings once per message (single DB query)
        const settings = await getAllSettings();

        // Extract sender phone
        const senderPnJid = m.sender?.pn || m.sender?.id || '';
        const senderPhone = senderPnJid.replace(/@.*$/, '');

        // Track whether scammer guard already handled this message
        // Fix: auto-reply must not fire after scammer warn/kick/block
        let scammerHandled = false;

        if (senderPhone) {
            // ── 1. Whitelist check ──────────────────────────────────────────
            const whitelisted = await isWhitelisted(senderPhone);

            if (!whitelisted) {
                // ── 2. Blacklist check ────────────────────────────────────────
                const scammer = await checkScammer(senderPhone);
                if (scammer) {
                    const blocked = await executeScammerAction({ sock, m, scammer, deviceId: id, triggeredBy: 'blacklist', settings });
                    scammerHandled = true;
                    if (blocked) continue;
                } else {
                    // ── 3. Keyword check ─────────────────────────────────────
                    const msgText = extractText(m);
                    const matchedKw = await checkKeywords(msgText);
                    if (matchedKw) {
                        consola.warn(`[ScammerGuard: ${id}] Keyword match: "${matchedKw.keyword}" from ${senderPhone}`);

                        // Bug #3 fix: Log ONCE here
                        await writeLog({
                            phone: senderPhone,
                            name: m.sender?.pushName || null,
                            groupJid: (m.key?.remoteJid || m.chat || '').endsWith('@g.us') ? (m.key?.remoteJid || m.chat || '') : null,
                            deviceId: id,
                            actionTaken: matchedKw.action,
                            triggeredBy: 'keyword',
                            matchedKeyword: matchedKw.keyword,
                            messagePreview: msgText,
                        });

                        await db.from('scammer_keywords').where('id', matchedKw.id)
                            .update({ hit_count: db.raw('hit_count + 1'), updated_at: DateTime.now().toISO() });

                        // skipLog:true — already logged above
                        const blocked = await executeScammerAction({
                            sock, m,
                            scammer: { phone: senderPhone, action: matchedKw.action, reason: `Keyword: ${matchedKw.keyword}`, id: null },
                            deviceId: id,
                            triggeredBy: 'keyword',
                            matchedKeyword: matchedKw.keyword,
                            skipLog: true,
                            settings,
                        });
                        scammerHandled = true;

                        // ── 4. Threshold check ───────────────────────────────
                        const autoBlacklisted = await checkThreshold(senderPhone, settings);
                        if (autoBlacklisted) {
                            await writeLog({
                                phone: senderPhone,
                                name: m.sender?.pushName || null,
                                deviceId: id,
                                actionTaken: 'threshold_blacklisted',
                                triggeredBy: 'threshold',
                                messagePreview: msgText,
                            });
                        }

                        if (blocked) continue;
                    }

                    // ── 5. AI Scam Detection ─────────────────────────────────
                    if (!scammerHandled) {
                        try {
                            const aiEnabled = getSetting(settings, 'ai_enabled', '0');
                            const aiScamEnabled = getSetting(settings, 'ai_scam_detect_enabled', '0');
                            if (aiEnabled === '1' && aiScamEnabled === '1') {
                                const aiApiKey = getSetting(settings, 'ai_api_key', '');
                                const aiMsgText = msgText || extractText(m);
                                if (aiApiKey && aiMsgText && aiMsgText.length > 10) {
                                    const aiThreshold = parseInt(getSetting(settings, 'ai_scam_detect_threshold', '80') || '80');
                                    const aiResult = await detectScam(aiApiKey, aiMsgText);
                                    if (aiResult.isScam && aiResult.confidence >= aiThreshold) {
                                        consola.warn(`[AI Scam: ${id}] ⚠️ Detected ${senderPhone} conf:${aiResult.confidence}% — ${aiResult.reason}`);
                                        const aiScamAction = getSetting(settings, 'ai_scam_action', 'warn');
                                        const aiChatJid = m.key?.remoteJid || m.chat;
                                        await writeLog({
                                            phone: senderPhone,
                                            name: m.sender?.pushName || null,
                                            groupJid: aiChatJid?.endsWith('@g.us') ? aiChatJid : null,
                                            deviceId: id,
                                            actionTaken: aiScamAction,
                                            triggeredBy: 'ai_detect',
                                            matchedKeyword: `AI (${aiResult.confidence}%): ${aiResult.reason}`,
                                            messagePreview: aiMsgText,
                                        });
                                        const blocked = await executeScammerAction({
                                            sock, m,
                                            scammer: {
                                                phone: senderPhone,
                                                action: aiScamAction,
                                                reason: `AI Detect (${aiResult.confidence}%): ${aiResult.reason}`,
                                                id: null,
                                            },
                                            deviceId: id,
                                            triggeredBy: 'ai_detect',
                                            skipLog: true,
                                            settings,
                                        });
                                        scammerHandled = true;
                                        if (blocked) continue;
                                    }
                                }
                            }
                        } catch (aiErr) {
                            consola.warn(`[AI Scam: ${id}] Error:`, aiErr.message);
                        }
                    }
                }
            }
        }

        // ── AI Auto-Reply ─────────────────────────────────────────────────────
        // Fix: skip if scammer guard already handled this message (warn/kick)
        // Fix: use m.msg.rawText (already parsed by serialize) instead of re-extracting
        if (!scammerHandled) {
            try {
                const aiEnabled = getSetting(settings, 'ai_enabled', '0');
                const aiAutoReplyEnabled = getSetting(settings, 'ai_auto_reply_enabled', '0');
                if (aiEnabled === '1' && aiAutoReplyEnabled === '1') {
                    // Use serialize's already-parsed text (more reliable than re-parsing raw proto)
                    const aiMsgText = m.msg?.rawText || extractText(m);
                    if (aiMsgText && aiMsgText.trim()) {
                        const prefix = (getSetting(settings, 'ai_auto_reply_prefix', '') || '').trim().toLowerCase();
                        const msgLower = aiMsgText.toLowerCase().trim();
                        const shouldReply = prefix ? msgLower.startsWith(prefix) : true;
                        if (shouldReply) {
                            const queryText = prefix
                                ? aiMsgText.substring(prefix.length).trim()
                                : aiMsgText.trim();
                            if (queryText) {
                                const aiApiKey = getSetting(settings, 'ai_api_key', '');
                                if (aiApiKey) {
                                    const sysPrompt = getSetting(settings, 'ai_auto_reply_system_prompt', '') || '';
                                    const chatJid = m.key?.remoteJid || m.chat;

                                    // Build messages array with context
                                    const messages = [];

                                    // Add quoted message as context if exists
                                    if (m.quoted) {
                                        const quotedText = m.quoted?.msg?.text || m.quoted?.text || '';
                                        if (quotedText) {
                                            messages.push({ role: 'user', content: quotedText });
                                            messages.push({ role: 'assistant', content: '(mesej sebelum ini yang dikuotakan oleh pengguna)' });
                                        }
                                    }

                                    messages.push({ role: 'user', content: queryText });

                                    consola.info(`[AI Reply: ${id}] Processing reply for ${chatJid}`);

                                    // Send typing indicator
                                    try { await sock.sendPresenceUpdate('composing', chatJid); } catch (_) {}

                                    const aiReply = await generateAutoReply(aiApiKey, queryText, sysPrompt, messages);

                                    // Stop typing indicator
                                    try { await sock.sendPresenceUpdate('paused', chatJid); } catch (_) {}

                                    if (aiReply) {
                                        const sendOpts = { text: aiReply };
                                        // Fix: Baileys quoted needs only { key, message } — not the full serialized object
                                        if (m.flags?.isGroup && m.key && m.message) {
                                            sendOpts.quoted = { key: m.key, message: m.message };
                                        }
                                        try {
                                            await sock.sendMessage(chatJid, sendOpts);
                                            consola.info(`[AI Reply: ${id}] ✅ Replied in ${chatJid}`);
                                        } catch (sendErr) {
                                            consola.warn(`[AI Reply: ${id}] sendMessage gagal: ${sendErr.message} — cuba tanpa quote`);
                                            // Fallback: send without quote if quoted reply fails
                                            await sock.sendMessage(chatJid, { text: aiReply });
                                            consola.info(`[AI Reply: ${id}] ✅ Replied (tanpa quote) in ${chatJid}`);
                                        }
                                    } else {
                                        consola.warn(`[AI Reply: ${id}] generateAutoReply returned null/empty`);
                                    }
                                } else {
                                    consola.warn(`[AI Reply: ${id}] Skipped — ai_api_key not configured`);
                                }
                            }
                        }
                    }
                }
            } catch (aiReplyErr) {
                consola.warn(`[AI Reply: ${id}] Error:`, aiReplyErr.message);
                try { await sock.sendPresenceUpdate('paused', m.key?.remoteJid || m.chat); } catch (_) {}
            }
        }

        // ── Webhook processing ────────────────────────────────────────────────
        if (m.webhook) {
            try {
                await Promise.all([
                    sock.readMessages([m.key]),
                    axios.post(m.webhook, m, { timeout: WEBHOOK_TIMEOUT }),
                ]);
                const duration = Date.now() - startTime;
                consola.success(`[WEBHOOK: ${id}] Successfully processed in ${duration}ms`);
            } catch (error) {
                let errorMessage = `[WEBHOOK: ${id}] Something went wrong.`;
                if (axios.isAxiosError(error)) {
                    if (error.response) {
                        errorMessage = `[WEBHOOK: ${id}] ${error.response.status} ${error.response.statusText}`;
                    } else if (error.code === 'ECONNABORTED') {
                        errorMessage = `[WEBHOOK: ${id}] Request timed out after ${WEBHOOK_TIMEOUT}.`;
                    } else {
                        errorMessage = `[WEBHOOK: ${id}] ${error.message}`;
                    }
                } else {
                    errorMessage = `[WEBHOOK: ${id}] ${error.message}`;
                }
                consola.error(errorMessage);
            }
        }
    }
}
