import ScammerGuard from '#models/scammer_guard'
import ScammerKeyword from '#models/scammer_keyword'
import ScammerWhitelist from '#models/scammer_whitelist'
import ScammerLog from '#models/scammer_log'
import Device from '#models/device'
import WASocketManager from '#wa/whatsapp'
import db from '@adonisjs/lucid/services/db'
import vine, { errors as vineErrors } from '@vinejs/vine'
import { DateTime } from 'luxon'
import { readFileSync, writeFileSync, unlinkSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { randomBytes } from 'crypto'

// Simple CSV helpers (no external deps)
function toCSV(rows) {
    if (!rows.length) return ''
    const headers = Object.keys(rows[0])
    const escape = v => {
        const s = String(v ?? '')
        if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s.replace(/"/g, '""')}"`
        return s
    }
    return [headers.join(','), ...rows.map(r => headers.map(h => escape(r[h])).join(','))].join('\n')
}

function fromCSV(text) {
    const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n').filter(l => l.trim())
    if (lines.length < 2) return []
    const headers = lines[0].split(',').map(h => h.trim().replace(/^"|"$/g, ''))
    return lines.slice(1).map(line => {
        const vals = []
        let cur = '', inQ = false
        for (let i = 0; i < line.length; i++) {
            if (line[i] === '"') {
                if (inQ && line[i+1] === '"') { cur += '"'; i++ }
                else inQ = !inQ
            } else if (line[i] === ',' && !inQ) { vals.push(cur); cur = '' }
            else cur += line[i]
        }
        vals.push(cur)
        return Object.fromEntries(headers.map((h, i) => [h, (vals[i] || '').trim()]))
    })
}

// Batch-load settings (single query)
async function getAllSettingsMap() {
    try {
        const rows = await db.from('scammer_settings')
        const map = {}
        for (const r of rows) map[r.key] = r.value
        return map
    } catch { return {} }
}

// Helper: get scammer settings
async function getSetting(key, fallback = null) {
    try {
        const row = await db.from('scammer_settings').where('key', key).first()
        return row ? row.value : fallback
    } catch { return fallback }
}

async function setSetting(key, value) {
    const existing = await db.from('scammer_settings').where('key', key).first()
    if (existing) {
        await db.from('scammer_settings').where('key', key).update({ value, updated_at: DateTime.now().toISO() })
    } else {
        await db.from('scammer_settings').insert({ key, value, created_at: DateTime.now().toISO(), updated_at: DateTime.now().toISO() })
    }
}

export default class ScammerController {

    // ─── MAIN INDEX ─────────────────────────────────────────────────────────────

    async index({ view, auth, request }) {
        const tab = request.input('tab', 'blacklist')
        const page = parseInt(request.input('page', '1')) || 1
        const perPage = 20

        // Bulk expire in one query
        const now = DateTime.now().toISO()
        await db.from('scammer_guards')
            .where('is_active', true)
            .whereNotNull('expires_at')
            .where('expires_at', '<', now)
            .update({ is_active: false, updated_at: now })

        const scammers = await ScammerGuard.query().orderBy('created_at', 'desc')
        const totalCount = scammers.length
        const activeCount = scammers.filter(s => s.isActive).length
        const inactiveCount = scammers.filter(s => !s.isActive).length
        const totalHits = scammers.reduce((a, s) => a + (parseInt(s.hitCount) || 0), 0)

        // Keywords
        const keywords = await ScammerKeyword.query().orderBy('created_at', 'desc')

        // Whitelist
        const whitelist = await ScammerWhitelist.query().orderBy('created_at', 'desc')

        // Logs (paginated)
        const logsTotal = await db.from('scammer_logs').count('id as total').first()
        const logsTotalCount = parseInt(logsTotal?.total || 0)
        const logsOffset = (page - 1) * perPage
        const logs = await db.from('scammer_logs')
            .orderBy('created_at', 'desc')
            .limit(perPage)
            .offset(logsOffset)
        const logsTotalPages = Math.ceil(logsTotalCount / perPage)

        // Batch-load all settings in one query
        const sm = await getAllSettingsMap()
        const gs = (k, d) => sm[k] !== undefined ? sm[k] : d

        const settings = {
            globalThreshold: gs('global_threshold', '0'),
            autoExpireDays: gs('auto_expire_days', '0'),
            sharedDbEnabled: gs('shared_db_enabled', '0'),
            sharedDbUrl: gs('shared_db_url', ''),
            sharedDbToken: gs('shared_db_token', ''),
            thresholdAction: gs('threshold_action', 'warn'),
            aiEnabled: gs('ai_enabled', '0'),
            aiApiKey: gs('ai_api_key', ''),
            aiAutoReplyEnabled: gs('ai_auto_reply_enabled', '0'),
            aiAutoReplyPrefix: gs('ai_auto_reply_prefix', ''),
            aiAutoReplySystemPrompt: gs('ai_auto_reply_system_prompt', ''),
            aiGreetingEnabled: gs('ai_greeting_enabled', '0'),
            aiGreetingPrompt: gs('ai_greeting_prompt', ''),
            aiScamDetectEnabled: gs('ai_scam_detect_enabled', '0'),
            aiScamDetectThreshold: gs('ai_scam_detect_threshold', '80'),
            aiScamAction: gs('ai_scam_action', 'warn'),
            // NEW settings
            autoReplyGroupOnly: gs('auto_reply_group_only', '0'),
            autoReplyPrivateOnly: gs('auto_reply_private_only', '0'),
            autoReplyCooldownSec: gs('auto_reply_cooldown_sec', '0'),
            autoReplyGroupMode: gs('auto_reply_group_mode', 'all'),
            autoReplyGroups: gs('auto_reply_groups', '[]'),
            greetingLeaveEnabled: gs('greeting_leave_enabled', '0'),
            greetingLeavePrompt: gs('greeting_leave_prompt', ''),
            scammerNotifyJid: gs('scammer_notify_jid', ''),
        }

        return view.render('scammer/index', {
            scammers, totalCount, activeCount, inactiveCount, totalHits,
            keywords, whitelist,
            logs, logsTotalCount, logsTotalPages, logsCurrentPage: page,
            settings, tab,
        })
    }

    // ─── BLACKLIST CRUD ──────────────────────────────────────────────────────────

    async store({ request, response, auth }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        try {
            const data = await request.validateUsing(vine.compile(vine.object({
                phone: vine.string().trim().maxLength(30),
                name: vine.string().trim().maxLength(100).optional(),
                reason: vine.string().trim().maxLength(500).optional(),
                action: vine.enum(['log', 'warn', 'kick', 'block']),
                notes: vine.string().trim().maxLength(500).optional(),
                expires_days: vine.number().min(0).max(3650).optional(),
                threshold: vine.number().min(0).max(999).optional(),
            })))

            const phone = data.phone.replace(/[\s\-\+]/g, '')
            const existing = await ScammerGuard.query().where('phone', phone).first()
            if (existing) return response.unprocessableEntity({ message: 'Nombor ini sudah ada dalam senarai hitam.' })

            let expiresAt = null
            if (data.expires_days && data.expires_days > 0) {
                expiresAt = DateTime.now().plus({ days: data.expires_days }).toISO()
            }
            if (!expiresAt) {
                const autoExpireDays = parseInt(await getSetting('auto_expire_days', '0'))
                if (autoExpireDays > 0) expiresAt = DateTime.now().plus({ days: autoExpireDays }).toISO()
            }

            await ScammerGuard.create({
                phone, name: data.name || null, reason: data.reason || null,
                action: data.action, isActive: true,
                addedBy: auth.use('web').user?.id || null,
                hitCount: 0, notes: data.notes || null, expiresAt,
                threshold: data.threshold || 0,
            })
            return response.ok({ message: 'Nombor berjaya ditambah ke senarai hitam.' })
        } catch (error) {
            if (error instanceof vineErrors.E_VALIDATION_ERROR) return response.unprocessableEntity({ message: 'Input tidak sah.', errors: error.messages })
            return response.internalServerError({ message: error.message })
        }
    }

    async update({ params, request, response }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        const scammer = await ScammerGuard.find(params.id)
        if (!scammer) return response.notFound({ message: 'Rekod tidak dijumpai.' })
        try {
            const data = await request.validateUsing(vine.compile(vine.object({
                name: vine.string().trim().maxLength(100).nullable().optional(),
                reason: vine.string().trim().maxLength(500).nullable().optional(),
                action: vine.enum(['log', 'warn', 'kick', 'block']).optional(),
                notes: vine.string().trim().maxLength(500).nullable().optional(),
                expires_days: vine.number().min(0).max(3650).optional(),
                threshold: vine.number().min(0).max(999).optional(),
            })))
            if (data.name !== undefined) scammer.name = data.name || null
            if (data.reason !== undefined) scammer.reason = data.reason || null
            if (data.action !== undefined) scammer.action = data.action
            if (data.notes !== undefined) scammer.notes = data.notes || null
            if (data.threshold !== undefined) scammer.threshold = data.threshold || 0
            if (data.expires_days !== undefined) {
                scammer.expiresAt = data.expires_days > 0
                    ? DateTime.now().plus({ days: data.expires_days }).toISO() : null
            }
            await scammer.save()
            return response.ok({ message: 'Rekod berjaya dikemaskini.' })
        } catch (error) {
            if (error instanceof vineErrors.E_VALIDATION_ERROR) return response.unprocessableEntity({ message: 'Input tidak sah.', errors: error.messages })
            return response.internalServerError({ message: error.message })
        }
    }

    async destroy({ params, response, request }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        const scammer = await ScammerGuard.find(params.id)
        if (!scammer) return response.notFound({ message: 'Rekod tidak dijumpai.' })
        await scammer.delete()
        return response.ok({ message: 'Nombor berjaya dipadamkan dari senarai hitam.' })
    }

    async toggle({ params, response, request }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        const scammer = await ScammerGuard.find(params.id)
        if (!scammer) return response.notFound({ message: 'Rekod tidak dijumpai.' })
        scammer.isActive = !scammer.isActive
        await scammer.save()
        return response.ok({
            message: scammer.isActive ? 'Nombor diaktifkan.' : 'Nombor dinyahaktifkan.',
            isActive: scammer.isActive,
        })
    }

    // ── NEW: Reset hit count ──────────────────────────────────────────────────
    async resetHits({ params, response, request }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        const scammer = await ScammerGuard.find(params.id)
        if (!scammer) return response.notFound({ message: 'Rekod tidak dijumpai.' })
        scammer.hitCount = 0
        scammer.lastSeenAt = null
        await scammer.save()
        return response.ok({ message: 'Hit count berjaya ditetapkan semula.' })
    }

    // ── NEW: Quick phone check ────────────────────────────────────────────────
    async check({ request, response }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        try {
            const data = await request.validateUsing(vine.compile(vine.object({
                phone: vine.string().trim().maxLength(30),
            })))
            const phone = data.phone.replace(/[\s\-\+]/g, '')
            const now = DateTime.now().toISO()

            const blacklisted = await db.from('scammer_guards')
                .where('phone', phone).where('is_active', true).first()
            const whitelisted = await db.from('scammer_whitelist').where('phone', phone).first()
            const logs = await db.from('scammer_logs')
                .where('phone', phone).orderBy('created_at', 'desc').limit(5)

            let status = 'clear'
            if (blacklisted) {
                if (blacklisted.expires_at && blacklisted.expires_at < now) status = 'expired'
                else status = 'blacklisted'
            } else if (whitelisted) {
                status = 'whitelisted'
            }

            return response.ok({
                phone,
                status,
                record: blacklisted || whitelisted || null,
                recentLogs: logs,
            })
        } catch (error) {
            if (error instanceof vineErrors.E_VALIDATION_ERROR) return response.unprocessableEntity({ message: 'Nombor telefon diperlukan.' })
            return response.internalServerError({ message: error.message })
        }
    }

    // ── NEW: Bulk actions ─────────────────────────────────────────────────────
    async bulkAction({ request, response }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        try {
            const data = await request.validateUsing(vine.compile(vine.object({
                ids: vine.array(vine.number()),
                action: vine.enum(['activate', 'deactivate', 'delete', 'change_action']),
                new_action: vine.enum(['log', 'warn', 'kick', 'block']).optional(),
            })))
            const { ids, action, new_action } = data
            if (!ids.length) return response.badRequest({ message: 'Tiada rekod dipilih.' })

            let affected = 0
            if (action === 'delete') {
                affected = await db.from('scammer_guards').whereIn('id', ids).delete()
                return response.ok({ message: `${affected} rekod berjaya dipadamkan.`, affected })
            }
            if (action === 'activate') {
                affected = await db.from('scammer_guards').whereIn('id', ids)
                    .update({ is_active: true, updated_at: DateTime.now().toISO() })
                return response.ok({ message: `${affected} rekod diaktifkan.`, affected })
            }
            if (action === 'deactivate') {
                affected = await db.from('scammer_guards').whereIn('id', ids)
                    .update({ is_active: false, updated_at: DateTime.now().toISO() })
                return response.ok({ message: `${affected} rekod dinyahaktifkan.`, affected })
            }
            if (action === 'change_action') {
                if (!new_action) return response.badRequest({ message: 'Tindakan baru diperlukan.' })
                affected = await db.from('scammer_guards').whereIn('id', ids)
                    .update({ action: new_action, updated_at: DateTime.now().toISO() })
                return response.ok({ message: `${affected} rekod dikemaskini ke tindakan "${new_action}".`, affected })
            }
            return response.badRequest({ message: 'Tindakan tidak dikenali.' })
        } catch (error) {
            if (error instanceof vineErrors.E_VALIDATION_ERROR) return response.unprocessableEntity({ message: 'Input tidak sah.', errors: error.messages })
            return response.internalServerError({ message: error.message })
        }
    }

    // ── NEW: Fetch all groups from connected devices for group selector ────────
    async fetchGroups({ response, auth }) {
        try {
            const devices = await Device.query().preload('accessTokens')
            const groups = []
            const seen = new Set()

            for (const device of devices) {
                const sessionKey = `${device.id}-${device.number}`
                const status = WASocketManager.SESSION_STATUS.get(sessionKey)
                if (status !== 'connected') continue

                const sock = WASocketManager.getSession(sessionKey)
                if (!sock) continue

                try {
                    const rawGroups = await sock.groupFetchAllParticipating()
                    for (const g of Object.values(rawGroups || {})) {
                        if (!g.id || seen.has(g.id)) continue
                        seen.add(g.id)
                        groups.push({
                            jid: g.id,
                            name: g.subject || g.id.split('@')[0],
                            device: device.number || device.id,
                            participantCount: g.participants?.length || 0,
                        })
                    }
                } catch (e) {
                    // Device connected but groupFetchAllParticipating failed — skip
                }
            }

            // Sort by name
            groups.sort((a, b) => a.name.localeCompare(b.name))
            return response.ok({ groups })
        } catch (e) {
            return response.internalServerError({ message: e.message })
        }
    }

    // ── NEW: Stats for dashboard ──────────────────────────────────────────────
    async stats({ response }) {
        try {
            const now = DateTime.now()
            const todayStart = now.startOf('day').toISO()
            const weekStart = now.minus({ days: 7 }).toISO()

            const [totalBlacklist] = await db.from('scammer_guards').count('id as c')
            const [activeBlacklist] = await db.from('scammer_guards').where('is_active', true).count('id as c')
            const [totalKeywords] = await db.from('scammer_keywords').count('id as c')
            const [activeKeywords] = await db.from('scammer_keywords').where('is_active', true).count('id as c')
            const [totalWhitelist] = await db.from('scammer_whitelist').count('id as c')
            const [totalLogs] = await db.from('scammer_logs').count('id as c')
            const [todayLogs] = await db.from('scammer_logs').where('created_at', '>=', todayStart).count('id as c')
            const [weekLogs] = await db.from('scammer_logs').where('created_at', '>=', weekStart).count('id as c')

            // Top triggered phones this week
            const topPhones = await db.from('scammer_logs')
                .where('created_at', '>=', weekStart)
                .select('phone').count('id as hits')
                .groupBy('phone').orderBy('hits', 'desc').limit(5)

            // Trigger breakdown
            const triggerBreakdown = await db.from('scammer_logs')
                .where('created_at', '>=', weekStart)
                .select('triggered_by').count('id as count')
                .groupBy('triggered_by')

            // Action breakdown
            const actionBreakdown = await db.from('scammer_logs')
                .where('created_at', '>=', weekStart)
                .select('action_taken').count('id as count')
                .groupBy('action_taken')

            return response.ok({
                blacklist: { total: parseInt(totalBlacklist?.c || 0), active: parseInt(activeBlacklist?.c || 0) },
                keywords: { total: parseInt(totalKeywords?.c || 0), active: parseInt(activeKeywords?.c || 0) },
                whitelist: { total: parseInt(totalWhitelist?.c || 0) },
                logs: {
                    total: parseInt(totalLogs?.c || 0),
                    today: parseInt(todayLogs?.c || 0),
                    week: parseInt(weekLogs?.c || 0),
                },
                topPhones,
                triggerBreakdown,
                actionBreakdown,
            })
        } catch (e) {
            return response.internalServerError({ message: e.message })
        }
    }

    // ─── EXPORT / IMPORT ────────────────────────────────────────────────────────

    async exportCsv({ response }) {
        const scammers = await ScammerGuard.query().orderBy('created_at', 'desc')
        const rows = scammers.map(s => ({
            phone: s.phone, name: s.name || '', reason: s.reason || '',
            action: s.action, is_active: s.isActive ? '1' : '0',
            hit_count: s.hitCount || 0, last_seen_at: s.lastSeenAt || '',
            expires_at: s.expiresAt || '', notes: s.notes || '',
            created_at: s.createdAt ? s.createdAt.toISO() : '',
        }))
        const csv = toCSV(rows)
        const tmpFile = join(tmpdir(), `scammer_export_${randomBytes(6).toString('hex')}.csv`)
        writeFileSync(tmpFile, csv, 'utf8')
        response.header('Content-Type', 'text/csv')
        response.header('Content-Disposition', `attachment; filename="scammer_blacklist_${DateTime.now().toFormat('yyyyMMdd_HHmm')}.csv"`)
        await response.download(tmpFile)
        try { unlinkSync(tmpFile) } catch (_) {}
    }

    async importCsv({ request, response, auth }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        const file = request.file('csv_file', { size: '5mb', extnames: ['csv'] })
        if (!file) return response.badRequest({ message: 'Fail CSV diperlukan.' })
        if (file.hasErrors) return response.unprocessableEntity({ message: file.errors[0]?.message || 'Fail tidak sah.' })

        const tmpPath = join(tmpdir(), `import_${randomBytes(6).toString('hex')}.csv`)
        await file.move(tmpdir(), { name: tmpPath.split('/').pop() })

        let added = 0, skipped = 0, errors = []
        const userId = auth.use('web').user?.id || null

        try {
            const text = readFileSync(tmpPath, 'utf8')
            const records = fromCSV(text)
            for (const row of records) {
                try {
                    const phone = (row.phone || '').replace(/[\s\-\+]/g, '')
                    if (!phone) { skipped++; continue }
                    const existing = await ScammerGuard.query().where('phone', phone).first()
                    if (existing) { skipped++; continue }
                    const action = ['log', 'warn', 'kick', 'block'].includes(row.action) ? row.action : 'log'
                    await ScammerGuard.create({
                        phone, name: row.name || null, reason: row.reason || null,
                        action, isActive: row.is_active !== '0',
                        addedBy: userId, hitCount: parseInt(row.hit_count) || 0,
                        lastSeenAt: row.last_seen_at || null,
                        expiresAt: row.expires_at || null,
                        notes: row.notes || null,
                        threshold: parseInt(row.threshold) || 0,
                    })
                    added++
                } catch (e) { errors.push(e.message) }
            }
            try { unlinkSync(tmpPath) } catch (_) {}
        } catch (e) {
            return response.internalServerError({ message: `Gagal baca CSV: ${e.message}` })
        }

        return response.ok({ message: `Import selesai: ${added} ditambah, ${skipped} dilangkau.`, added, skipped, errors })
    }

    // ─── KEYWORDS ───────────────────────────────────────────────────────────────

    async storeKeyword({ request, response, auth }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        try {
            const data = await request.validateUsing(vine.compile(vine.object({
                keyword: vine.string().trim().maxLength(200),
                action: vine.enum(['log', 'warn', 'kick', 'block']),
                notes: vine.string().trim().maxLength(500).optional(),
            })))
            const existing = await ScammerKeyword.query().where('keyword', data.keyword).first()
            if (existing) return response.unprocessableEntity({ message: 'Keyword ini sudah ada.' })
            await ScammerKeyword.create({
                keyword: data.keyword, action: data.action,
                notes: data.notes || null, isActive: true, hitCount: 0,
                addedBy: auth.use('web').user?.id || null,
            })
            return response.ok({ message: 'Keyword berjaya ditambah.' })
        } catch (error) {
            if (error instanceof vineErrors.E_VALIDATION_ERROR) return response.unprocessableEntity({ message: 'Input tidak sah.', errors: error.messages })
            return response.internalServerError({ message: error.message })
        }
    }

    // ── NEW: Edit keyword ─────────────────────────────────────────────────────
    async updateKeyword({ params, request, response }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        const kw = await ScammerKeyword.find(params.id)
        if (!kw) return response.notFound({ message: 'Keyword tidak dijumpai.' })
        try {
            const data = await request.validateUsing(vine.compile(vine.object({
                keyword: vine.string().trim().maxLength(200).optional(),
                action: vine.enum(['log', 'warn', 'kick', 'block']).optional(),
                notes: vine.string().trim().maxLength(500).nullable().optional(),
            })))
            if (data.keyword) {
                // Check duplicate if keyword changed
                if (data.keyword !== kw.keyword) {
                    const dup = await ScammerKeyword.query().where('keyword', data.keyword).whereNot('id', kw.id).first()
                    if (dup) return response.unprocessableEntity({ message: 'Keyword ini sudah wujud.' })
                }
                kw.keyword = data.keyword
            }
            if (data.action) kw.action = data.action
            if (data.notes !== undefined) kw.notes = data.notes || null
            await kw.save()
            return response.ok({ message: 'Keyword berjaya dikemaskini.' })
        } catch (error) {
            if (error instanceof vineErrors.E_VALIDATION_ERROR) return response.unprocessableEntity({ message: 'Input tidak sah.', errors: error.messages })
            return response.internalServerError({ message: error.message })
        }
    }

    async destroyKeyword({ params, response, request }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        const kw = await ScammerKeyword.find(params.id)
        if (!kw) return response.notFound({ message: 'Keyword tidak dijumpai.' })
        await kw.delete()
        return response.ok({ message: 'Keyword berjaya dipadamkan.' })
    }

    async toggleKeyword({ params, response, request }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        const kw = await ScammerKeyword.find(params.id)
        if (!kw) return response.notFound({ message: 'Keyword tidak dijumpai.' })
        kw.isActive = !kw.isActive
        await kw.save()
        return response.ok({ message: kw.isActive ? 'Keyword diaktifkan.' : 'Keyword dinyahaktifkan.', isActive: kw.isActive })
    }

    // ─── WHITELIST ───────────────────────────────────────────────────────────────

    async storeWhitelist({ request, response, auth }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        try {
            const data = await request.validateUsing(vine.compile(vine.object({
                phone: vine.string().trim().maxLength(30),
                name: vine.string().trim().maxLength(100).optional(),
                reason: vine.string().trim().maxLength(500).optional(),
            })))
            const phone = data.phone.replace(/[\s\-\+]/g, '')
            const existing = await ScammerWhitelist.query().where('phone', phone).first()
            if (existing) return response.unprocessableEntity({ message: 'Nombor ini sudah ada dalam whitelist.' })
            await ScammerWhitelist.create({
                phone, name: data.name || null, reason: data.reason || null,
                addedBy: auth.use('web').user?.id || null,
            })
            return response.ok({ message: 'Nombor berjaya ditambah ke whitelist.' })
        } catch (error) {
            if (error instanceof vineErrors.E_VALIDATION_ERROR) return response.unprocessableEntity({ message: 'Input tidak sah.', errors: error.messages })
            return response.internalServerError({ message: error.message })
        }
    }

    async destroyWhitelist({ params, response, request }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        const entry = await ScammerWhitelist.find(params.id)
        if (!entry) return response.notFound({ message: 'Rekod tidak dijumpai.' })
        await entry.delete()
        return response.ok({ message: 'Nombor berjaya dipadamkan dari whitelist.' })
    }

    // ─── LOGS ────────────────────────────────────────────────────────────────────

    async clearLogs({ response, request }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        await db.from('scammer_logs').delete()
        return response.ok({ message: 'Semua log telah dipadamkan.' })
    }

    async exportLogs({ response }) {
        const logs = await db.from('scammer_logs').orderBy('created_at', 'desc')
        const csv = toCSV(logs)
        const tmpFile = join(tmpdir(), `scammer_logs_${randomBytes(6).toString('hex')}.csv`)
        writeFileSync(tmpFile, csv, 'utf8')
        response.header('Content-Type', 'text/csv')
        response.header('Content-Disposition', `attachment; filename="scammer_logs_${DateTime.now().toFormat('yyyyMMdd_HHmm')}.csv"`)
        await response.download(tmpFile)
        try { unlinkSync(tmpFile) } catch (_) {}
    }

    // ─── SETTINGS ────────────────────────────────────────────────────────────────

    async saveSettings({ request, response }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        try {
            const data = await request.validateUsing(vine.compile(vine.object({
                global_threshold: vine.string().trim().optional(),
                auto_expire_days: vine.string().trim().optional(),
                shared_db_enabled: vine.string().trim().optional(),
                shared_db_url: vine.string().trim().maxLength(500).nullable().optional(),
                shared_db_token: vine.string().trim().maxLength(500).nullable().optional(),
                threshold_action: vine.enum(['log', 'warn', 'kick', 'block']).optional(),
                // AI Settings
                ai_enabled: vine.string().trim().optional(),
                ai_api_key: vine.string().trim().maxLength(200).nullable().optional(),
                ai_auto_reply_enabled: vine.string().trim().optional(),
                ai_auto_reply_prefix: vine.string().trim().maxLength(50).nullable().optional(),
                ai_auto_reply_system_prompt: vine.string().trim().maxLength(2000).nullable().optional(),
                ai_greeting_enabled: vine.string().trim().optional(),
                ai_greeting_prompt: vine.string().trim().maxLength(2000).nullable().optional(),
                ai_scam_detect_enabled: vine.string().trim().optional(),
                ai_scam_detect_threshold: vine.string().trim().optional(),
                ai_scam_action: vine.enum(['log', 'warn', 'kick', 'block']).optional(),
                // NEW settings
                auto_reply_group_only: vine.string().trim().optional(),
                auto_reply_private_only: vine.string().trim().optional(),
                auto_reply_cooldown_sec: vine.string().trim().optional(),
                auto_reply_group_mode: vine.enum(['all', 'selected']).optional(),
                auto_reply_groups: vine.string().trim().nullable().optional(), // JSON array of JIDs
                greeting_leave_enabled: vine.string().trim().optional(),
                greeting_leave_prompt: vine.string().trim().maxLength(2000).nullable().optional(),
                scammer_notify_jid: vine.string().trim().maxLength(100).nullable().optional(),
            })))
            for (const [key, value] of Object.entries(data)) {
                if (value !== undefined) await setSetting(key, value === null ? '' : value)
            }
            return response.ok({ message: 'Tetapan berjaya disimpan.' })
        } catch (error) {
            if (error instanceof vineErrors.E_VALIDATION_ERROR) return response.unprocessableEntity({ message: 'Input tidak sah.', errors: error.messages })
            return response.internalServerError({ message: error.message })
        }
    }

    // ─── AI KEY TEST ─────────────────────────────────────────────────────────────

    async testAiKey({ request, response }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        try {
            const { apiKey } = await request.validateUsing(vine.compile(vine.object({
                apiKey: vine.string().trim().minLength(10),
            })))
            const { testApiKey } = await import('#services/deepseek_service')
            const result = await testApiKey(apiKey)
            if (result.valid) return response.ok({ message: result.message })
            else return response.badRequest({ message: result.message })
        } catch (error) {
            if (error instanceof vineErrors.E_VALIDATION_ERROR) return response.unprocessableEntity({ message: 'API key diperlukan.' })
            return response.internalServerError({ message: error.message })
        }
    }

    // ─── SHARED DB SYNC ──────────────────────────────────────────────────────────

    async syncSharedDb({ response, request }) {
        if (!request.ajax()) return response.badRequest('Invalid request.')
        const enabled = await getSetting('shared_db_enabled', '0')
        if (enabled !== '1') return response.badRequest({ message: 'Shared DB tidak diaktifkan.' })
        const sharedUrl = await getSetting('shared_db_url', '')
        const sharedToken = await getSetting('shared_db_token', '')
        if (!sharedUrl) return response.badRequest({ message: 'URL Shared DB tidak dikonfigurasi.' })

        try {
            const { default: axios } = await import('axios')
            const res = await axios.get(`${sharedUrl}/api/scammer-guard`, {
                headers: sharedToken ? { Authorization: `Bearer ${sharedToken}` } : {},
                timeout: 10000,
            })
            const remoteList = res.data?.data || []
            let added = 0, skipped = 0
            for (const entry of remoteList) {
                const phone = String(entry.phone || '').replace(/[\s\-\+]/g, '')
                if (!phone) { skipped++; continue }
                const existing = await ScammerGuard.query().where('phone', phone).first()
                if (existing) { skipped++; continue }
                await ScammerGuard.create({
                    phone, name: entry.name || null,
                    reason: entry.reason || '[Shared DB]',
                    action: ['log', 'warn', 'kick', 'block'].includes(entry.action) ? entry.action : 'warn',
                    isActive: true, hitCount: 0, notes: 'Disync dari Shared DB',
                })
                added++
            }
            return response.ok({ message: `Sync selesai: ${added} ditambah, ${skipped} dilangkau.`, added, skipped })
        } catch (e) {
            return response.internalServerError({ message: `Sync gagal: ${e.message}` })
        }
    }
}