import express from 'express'
import {
    makeWASocket,
    useMultiFileAuthState,
    fetchLatestBaileysVersion,
    DisconnectReason
} from '@whiskeysockets/baileys'
import fs from 'fs'
import path from 'path'
import axios from 'axios'
import mime from 'mime-types'
import { toDataURL } from 'qrcode'

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

// ===============================
// GLOBAL STORAGE
// ===============================
const sessions = {}
const sessionSettings = {}
const AUTH_BASE = './auth'

// ===============================
// UTIL
// ===============================
function deleteAuth(sessionId) {
    const p = path.join(AUTH_BASE, sessionId)
    if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true })
}

// ===============================
// CREATE SOCKET
// ===============================
async function createSocket(sessionId, state, saveCreds) {
    const { version } = await fetchLatestBaileysVersion()

    const sock = makeWASocket({
        version,
        auth: state,
        printQRInTerminal: false,
        browser: ['Chrome (Windows)', 'Chrome', '10'],
        syncFullHistory: false,
        markOnlineOnConnect: true,
        keepAliveIntervalMs: 20000
    })

    sock.ev.on('creds.update', saveCreds)
    return sock
}

// ===============================
// START SESSION
// ===============================
async function startSession(sessionId, fresh = false) {
    if (fresh) deleteAuth(sessionId)

    const authDir = path.join(AUTH_BASE, sessionId)
    const { state, saveCreds } = await useMultiFileAuthState(authDir)

    const sock = await createSocket(sessionId, state, saveCreds)

    sessions[sessionId] = { socket: sock, qr: null }
    sessionSettings[sessionId] = sessionSettings[sessionId] || {
        autoCallReject: false
    }

    sock.ev.on('connection.update', async (update) => {
        const { connection, lastDisconnect, qr } = update

        if (qr) sessions[sessionId].qr = await toDataURL(qr)

        if (connection === 'open') {
            console.log(`[${sessionId}] Connected`)
            sessions[sessionId].qr = null
        }

        if (connection === 'close') {
            const code =
                lastDisconnect?.error?.output?.statusCode ||
                lastDisconnect?.error?.statusCode ||
                0

            console.log(`[${sessionId}] Disconnected: ${code}`)

            if (code === DisconnectReason.loggedOut || code === 401) {
                console.log(`[${sessionId}] Logged out → Fresh login`)
                return startSession(sessionId, true)
            }

            return startSession(sessionId)
        }
    })

    // AUTO CALL REJECT
    sock.ev.on('call', async (calls) => {
        const enabled = sessionSettings[sessionId]?.autoCallReject
        if (!enabled) return

        for (const c of calls) {
            if (c.status === 'offer') {
                try {
                    await sock.rejectCall(c.id, c.from)
                    await sock.sendMessage(c.from, {
                        text: "❌ Calls are disabled on this number."
                    })
                } catch (err) {
                    console.log("Call reject error:", err)
                }
            }
        }
    })

    return sock
}

// ===============================
// RESTORE ON SERVER START
// ===============================
async function restoreAllSessions() {
    if (!fs.existsSync(AUTH_BASE)) return

    const ids = fs.readdirSync(AUTH_BASE).filter(dir =>
        fs.lstatSync(path.join(AUTH_BASE, dir)).isDirectory()
    )

    for (const id of ids) {
        try {
            await startSession(id)
            console.log(`[${id}] restored`)
        } catch (err) {
            console.log(`Restore failed:`, err.message)
        }
    }
}

restoreAllSessions()

// ===============================
// ROUTES
// ===============================

// Toggle Auto Call Reject
app.post('/session/:id/auto-call', (req, res) => {
    const id = req.params.id
    const enable = req.query.enable === 'true'

    sessionSettings[id] = sessionSettings[id] || {}
    sessionSettings[id].autoCallReject = enable

    res.json({
        status: true,
        sessionId: id,
        autoCallReject: enable,
        message: `Auto-call reject ${enable ? 'enabled' : 'disabled'}`
    })
})

// Start Session
app.post('/session/:id', async (req, res) => {
    try {
        await startSession(req.params.id)
        res.json({ status: true, message: 'Session started' })
    } catch (err) {
        res.status(500).json({ status: false, message: err.message })
    }
})

// Get QR
app.get('/session/:id/qr', (req, res) => {
    const s = sessions[req.params.id]
    if (!s) return res.status(404).json({ status: false, message: 'Session not found' })

    res.json({
        status: true,
        qr: s.qr,
        message: s.qr ? 'Scan QR' : 'Connected'
    })
})

// SEND TEXT
app.post('/send', async (req, res) => {
    const { sessionId, number, message } = req.body
    const s = sessions[sessionId]
    if (!s) return res.status(404).json({ status: false, message: 'Session not found' })

    try {
        await s.socket.sendMessage(`${number}@s.whatsapp.net`, { text: message })
        res.json({ status: true, message: 'Sent' })
    } catch (err) {
        res.status(500).json({ status: false, message: err.message })
    }
})

// SEND IMAGE (NEW)
app.post('/send-image', async (req, res) => {
    const { sessionId, number, url, caption } = req.body

    const s = sessions[sessionId]
    if (!s) return res.status(404).json({ status: false, message: 'Session not found' })

    try {
        const fileRes = await axios.get(url, { responseType: 'arraybuffer' })
        const buffer = Buffer.from(fileRes.data)

        await s.socket.sendMessage(`${number}@s.whatsapp.net`, {
            image: buffer,
            caption: caption || ''
        })

        res.json({ status: true, message: 'Image sent' })
    } catch (err) {
        res.status(500).json({ status: false, message: err.message })
    }
})

// SEND DOCUMENT
app.post('/send-media', async (req, res) => {
    const { sessionId, number, url, caption } = req.body
    const s = sessions[sessionId]
    if (!s) return res.status(404).json({ status: false, message: 'Session not found' })

    try {
        const fileRes = await axios.get(url, { responseType: 'arraybuffer' })
        const buffer = Buffer.from(fileRes.data)
        const mimeType = fileRes.headers['content-type'] || mime.lookup(url)

        await s.socket.sendMessage(`${number}@s.whatsapp.net`, {
            document: buffer,
            mimetype: mimeType,
            fileName: url.split('/').pop(),
            caption: caption || ''
        })

        res.json({ status: true, message: 'Document sent' })
    } catch (err) {
        res.status(500).json({ status: false, message: err.message })
    }
})

// SEND BUTTONS
app.post('/send-buttons', async (req, res) => {
    const { sessionId, number, text, footer, buttons } = req.body
    const s = sessions[sessionId]
    if (!s) return res.status(404).json({ status: false, message: 'Session not found' })

    try {
        await s.socket.sendMessage(`${number}@s.whatsapp.net`, {
            text,
            footer,
            buttons,
            headerType: 1
        })
        res.json({ status: true, message: 'Sent' })
    } catch (err) {
        res.status(500).json({ status: false, message: err.message })
    }
})

// GET GROUPS
app.get('/session/:id/groups', async (req, res) => {
    const id = req.params.id
    const s = sessions[id]
    if (!s) return res.status(404).json({ status: false, message: 'Session not found' })

    try {
        const groups = await s.socket.groupFetchAllParticipating()
        const result = Object.values(groups).map(g => ({
            id: g.id,
            name: g.subject,
            participants: g.participants?.length || 0
        }))
        res.json({ status: true, groups: result })
    } catch (err) {
        res.status(500).json({ status: false, message: err.message })
    }
})

// SEND GROUP MESSAGE
app.post('/send-group', async (req, res) => {
    const { sessionId, groupId, message } = req.body

    const s = sessions[sessionId]
    if (!s) return res.status(404).json({ status: false, message: 'Session not found' })

    try {
        await s.socket.sendMessage(groupId, { text: message })
        res.json({ status: true, message: 'Group message sent' })
    } catch (err) {
        res.status(500).json({ status: false, message: err.message })
    }
})

// RESTART SESSION
app.post('/session/:id/restart', async (req, res) => {
    const id = req.params.id
    const s = sessions[id]
    if (s?.socket?.ws) s.socket.ws.close()

    try {
        await startSession(id)
        res.json({ status: true, message: 'Session restarted' })
    } catch (err) {
        res.status(500).json({ status: false, message: err.message })
    }
})

// RESET SESSION
app.post('/session/:id/reset', async (req, res) => {
    const id = req.params.id
    deleteAuth(id)
    delete sessions[id]

    try {
        await startSession(id)
        res.json({ status: true, message: 'Session reset → fresh QR' })
    } catch (err) {
        res.status(500).json({ status: false, message: err.message })
    }
})

// DELETE SESSION
app.delete('/session/:id', (req, res) => {
    const id = req.params.id
    const s = sessions[id]

    try {
        if (s?.socket?.ws) s.socket.ws.close()
        deleteAuth(id)
        delete sessions[id]

        res.json({ status: true, message: 'Session deleted' })
    } catch (err) {
        res.status(500).json({ status: false, message: err.message })
    }
})

// LIST SESSIONS
app.get('/sessions', (req, res) => {
    const data = Object.keys(sessions).map(id => ({
        sessionId: id,
        connected: sessions[id].qr === null
    }))
    res.json({ status: true, sessions: data })
})

// HEALTH
app.get('/health', (req, res) => {
    res.json({ status: true, message: 'OK' })
})

// CRON KEEPALIVE
app.get('/cron/keepalive', async (req, res) => {
    const report = []

    for (const id of Object.keys(sessions)) {
        const sess = sessions[id]
        const sock = sess?.socket
        const ws = sock?.ws
        const state = ws?.readyState

        if (state === 0) {
            report.push({ sessionId: id, status: "connecting" })
            continue
        }

        if (state === 1) {
            try {
                ws.ping()
                report.push({ sessionId: id, status: "active", action: "ping sent" })
            } catch (err) {
                report.push({ sessionId: id, status: "ping-error", error: err.message })
            }
            continue
        }

        if (state === 3 || !ws) {
            report.push({ sessionId: id, status: "closed", action: "restarting" })
            try {
                await startSession(id)
                report.push({ sessionId: id, status: "restarted" })
            } catch (err) {
                report.push({ sessionId: id, status: "restart-error", error: err.message })
            }
        }
    }

    res.json({ status: true, cron: "keepalive", sessions: report })
})

// ===============================
// START SERVER
// ===============================
app.listen(3000, () =>
    console.log(`WhatsApp API Server running on http://localhost:3000`)
)
