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;
}

function extractText(m) {
    try {
        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 per-scammer threshold if set (> 0), otherwise falls back to 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, '');

        // Check if already blacklisted — get per-scammer threshold if record exists
        const existing = await db.from('scammer_guards').where('phone', normalized).first();
        if (existing) return false; // Already blacklisted, nothing to do

        // Count keyword-triggered logs for this phone
        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);

        // Use global threshold (per-scammer threshold only applies to existing blacklist entries)
        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 added — keyword handler logs before calling this,
 * so we pass skipLog:true to avoid double-logging keyword events.
 * 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}`);

    // Update hit count
    if (scammer.id) await updateScammerHit(scammer.id);

    // Bug #3 fix: Only write log here if not already logged by the caller
    if (!skipLog) {
        await writeLog({
            phone,
            name: senderName,
            groupJid: isGroup ? chatJid : null,
            groupName: null,
            deviceId,
            actionTaken: action,
            triggeredBy: triggeredBy || 'blacklist',
            matchedKeyword: matchedKeyword || null,
            messagePreview: msgText,
        });
    }

    // Sync to shared DB (async, non-blocking)
    syncToSharedDb(phone, senderName, scammer.reason, settings).catch(() => {});

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

    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;
    }

    if (action === 'kick') {
        if (!isGroup) { consola.warn(`[ScammerGuard: ${deviceId}] Kick skipped — not a group`); return; }
        if (!senderJid) { consola.warn(`[ScammerGuard: ${deviceId}] Kick failed — no senderJid`); return; }
        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;
    }

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

// ─── 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(/@.*$/, '');

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

            if (!whitelisted) {
                // ── 2. Blacklist check ────────────────────────────────────────
                const scammer = await checkScammer(senderPhone);
                if (scammer) {
                    await executeScammerAction({ sock, m, scammer, deviceId: id, triggeredBy: 'blacklist', settings });
                    if (scammer.action === 'block') 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 (authoritative log for keyword events)
                        await writeLog({
                            phone: senderPhone,
                            name: m.sender?.pushName || null,
                            groupJid: (m.key?.remoteJid || '').endsWith('@g.us') ? (m.key?.remoteJid || '') : null,
                            deviceId: id,
                            actionTaken: matchedKw.action,
                            triggeredBy: 'keyword',
                            matchedKeyword: matchedKw.keyword,
                            messagePreview: msgText,
                        });

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

                        // Execute keyword action — skipLog:true to prevent double-logging (Bug #3 fix)
                        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,
                        });

                        // ── 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 (matchedKw.action === 'block') continue;
                    }

                    // ── 5. AI Scam Detection ─────────────────────────────
                    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,
                                    });
                                    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,
                                    });
                                    if (aiScamAction === 'block') continue;
                                }
                            }
                        }
                    } catch (aiErr) {
                        consola.warn(`[AI Scam: ${id}] Error:`, aiErr.message);
                    }
                }
            }
        }

        // ── AI Auto-Reply ─────────────────────────────────────────────────────
        try {
            const aiEnabled = getSetting(settings, 'ai_enabled', '0');
            const aiAutoReplyEnabled = getSetting(settings, 'ai_auto_reply_enabled', '0');
            if (aiEnabled === '1' && aiAutoReplyEnabled === '1') {
                const aiMsgText = extractText(m);
                if (aiMsgText) {
                    const prefix = (getSetting(settings, 'ai_auto_reply_prefix', '') || '').trim().toLowerCase();
                    const shouldReply = prefix
                        ? aiMsgText.toLowerCase().startsWith(prefix)
                        : true;
                    if (shouldReply) {
                        const queryText = prefix
                            ? aiMsgText.substring(prefix.length).trim()
                            : aiMsgText;
                        if (queryText) {
                            const aiApiKey = getSetting(settings, 'ai_api_key', '');
                            if (aiApiKey) {
                                const sysPrompt = getSetting(settings, 'ai_auto_reply_system_prompt', '') || '';
                                const aiReply = await generateAutoReply(aiApiKey, queryText, sysPrompt);
                                if (aiReply) {
                                    const chatJid = m.key?.remoteJid || m.chat;
                                    await sock.sendMessage(chatJid, { text: aiReply });
                                    consola.info(`[AI Reply: ${id}] ✅ Replied in ${chatJid}`);
                                }
                            }
                        }
                    }
                }
            }
        } catch (aiReplyErr) {
            consola.warn(`[AI Reply: ${id}] Error:`, aiReplyErr.message);
        }

        // ── 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);
            }
        }
    }
}
