import baileysPkg, { 
    DisconnectReason, 
    initAuthCreds,
    BufferJSON,
    proto,
    fetchLatestWaWebVersion,
    type WASocket,
    type ConnectionState,
    type AuthenticationCreds,
    type AuthenticationState,
    type SignalDataTypeMap
} from '@whiskeysockets/baileys';
const makeWASocket = (baileysPkg as any).default || baileysPkg;
const Browsers = (baileysPkg as any).default?.Browsers || (baileysPkg as any).Browsers;
import { Boom } from '@hapi/boom';
import pino from 'pino';
import { poolInstance } from './db';

export type AccountStatus = 'disconnected' | 'connecting' | 'connected' | 'qr_ready';

export type VCardContact = { name: string; phoneNumber: string };
export type OnContactsReceived = (accountId: number, senderJid: string, contacts: VCardContact[]) => void;

async function usePostgresAuthState(accountId: number): Promise<{
    state: AuthenticationState;
    saveCreds: () => Promise<void>;
    clearState: () => Promise<void>;
}> {
    const pool = poolInstance();

    const writeData = async (key: string, data: any) => {
        const value = JSON.stringify(data, BufferJSON.replacer);
        await pool.query(
            `INSERT INTO whatsapp_auth_data (account_id, data_key, data_value) VALUES ($1, $2, $3)
             ON CONFLICT (account_id, data_key) DO UPDATE SET data_value = $3`,
            [accountId, key, value]
        );
    };

    const readData = async (key: string): Promise<any> => {
        try {
            const result = await pool.query(
                `SELECT data_value FROM whatsapp_auth_data WHERE account_id = $1 AND data_key = $2`,
                [accountId, key]
            );
            if (result.rows.length === 0 || !result.rows[0].data_value) return null;
            return JSON.parse(result.rows[0].data_value, BufferJSON.reviver);
        } catch {
            return null;
        }
    };

    const removeData = async (key: string) => {
        await pool.query(
            `DELETE FROM whatsapp_auth_data WHERE account_id = $1 AND data_key = $2`,
            [accountId, key]
        );
    };

    const clearState = async () => {
        await pool.query(
            `DELETE FROM whatsapp_auth_data WHERE account_id = $1`,
            [accountId]
        );
    };

    const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds();

    return {
        state: {
            creds,
            keys: {
                get: async <T extends keyof SignalDataTypeMap>(type: T, ids: string[]) => {
                    const data: { [id: string]: SignalDataTypeMap[T] } = {};
                    await Promise.all(
                        ids.map(async (id) => {
                            let value = await readData(`${type}-${id}`);
                            if (type === 'app-state-sync-key' && value) {
                                value = proto.Message.AppStateSyncKeyData.fromObject(value);
                            }
                            data[id] = value;
                        })
                    );
                    return data;
                },
                set: async (data: any) => {
                    const tasks: Promise<void>[] = [];
                    for (const category in data) {
                        for (const id in data[category]) {
                            const value = data[category][id];
                            const key = `${category}-${id}`;
                            tasks.push(value ? writeData(key, value) : removeData(key));
                        }
                    }
                    await Promise.all(tasks);
                }
            }
        },
        saveCreds: async () => {
            await writeData('creds', creds);
        },
        clearState,
    };
}

export class WhatsAppSession {
    sock: WASocket | undefined;
    qr: string | undefined;
    status: AccountStatus = 'disconnected';
    id: number;
    name: string;
    connectedJid?: string;
    onContactsReceived?: OnContactsReceived;
    onConnected?: () => void;
    onPermanentDisconnect?: (accountId: number) => void;
    onCheckEventActive?: () => Promise<boolean>;
    private clearStateFn?: () => Promise<void>;
    private retryCount: number = 0;
    private freshStartFailures: number = 0;
    private permanentDisconnectSent: boolean = false;
    private static MAX_RETRIES = 6;
    private static MAX_FRESH_FAILURES = 2;

    constructor(id: number, name: string) {
        this.id = id;
        this.name = name;
    }

    async start() {
        try {
            this.status = 'connecting';
            this.retryCount = 0;
            this.permanentDisconnectSent = false; // Fresh connection attempt — reset notification flag
            await this.initialize();
        } catch (error) {
            console.error(`WhatsApp session ${this.id} (${this.name}) init failed:`, error);
            this.status = 'disconnected';
        }
    }

    async initialize() {
        if (this.sock) {
            try {
                this.sock.ev.removeAllListeners('connection.update');
                this.sock.ev.removeAllListeners('creds.update');
                this.sock.ev.removeAllListeners('messages.upsert');
                this.sock.ws.close();
            } catch {}
            this.sock = undefined;
        }

        const { state, saveCreds, clearState } = await usePostgresAuthState(this.id);
        this.clearStateFn = clearState;

        let waVersion: [number, number, number] | undefined;
        try {
            const { version } = await fetchLatestWaWebVersion({});
            waVersion = version;
        } catch (err) {
            console.warn(`Failed to fetch latest WA version, using default`);
        }

        const socketOpts: any = {
            auth: state,
            printQRInTerminal: false,
            logger: pino({ level: 'silent' }) as any,
            browser: Browsers?.macOS?.("Chrome") || ["Mac OS X", "Chrome", "120.0.0"],
            connectTimeoutMs: 180000,
            qrTimeout: 900000,
        };
        if (waVersion) {
            socketOpts.version = waVersion;
        }

        this.sock = makeWASocket(socketOpts);

        this.sock.ev.on('connection.update', (update: Partial<ConnectionState>) => {
            const { connection, lastDisconnect, qr } = update;

            if (qr) {
                this.qr = qr;
                this.status = 'qr_ready';
                console.log(`QR Code received for account ${this.id} (${this.name})`);
            }

            if (connection === 'close') {
                const boomError = lastDisconnect?.error as Boom;
                const statusCode = boomError?.output?.statusCode;
                const errorMessage = boomError?.message || boomError?.output?.payload?.message || 'unknown';
                const isLoggedOut = statusCode === DisconnectReason.loggedOut;
                const isSessionInvalid = statusCode === 405 || statusCode === 403;
                const isRestartRequired = statusCode === DisconnectReason.restartRequired;
                const isConflict = statusCode === 440;
                console.log(`Account ${this.id} (${this.name}) connection closed, status: ${statusCode}, msg: "${errorMessage}", loggedOut: ${isLoggedOut}, retry: ${this.retryCount}`);
                
                this.status = 'disconnected';
                this.qr = undefined;
                this.sock = undefined;

                // Helper: send at most one permanent-disconnect notification per episode
                const notifyDisconnect = () => {
                    if (!this.permanentDisconnectSent) {
                        this.permanentDisconnectSent = true;
                        this.onPermanentDisconnect?.(this.id);
                    }
                };

                if (isConflict) {
                    // Another device connected — reconnect silently after 30s
                    console.log(`Account ${this.id} (${this.name}) conflict detected (440), retrying in 30s...`);
                    this.status = 'connecting';
                    setTimeout(() => this.initialize(), 30000);
                    return;
                }

                if (isLoggedOut) {
                    // User intentionally removed device from WhatsApp — notify once and stop.
                    // No fresh-start attempts: the user must come back to the dashboard to reconnect.
                    console.log(`Account ${this.id} (${this.name}) logged out by user — notifying once and stopping.`);
                    notifyDisconnect();
                    return;
                }

                if (isSessionInvalid) {
                    // Server-side session issue (405/403) — try a fresh start silently, no notification.
                    this.freshStartFailures++;
                    if (this.freshStartFailures > WhatsAppSession.MAX_FRESH_FAILURES) {
                        console.log(`Account ${this.id} (${this.name}) session invalid (${statusCode}), too many fresh start failures. Stopping silently.`);
                        return; // Show as disconnected in UI — no notification spam
                    }
                    this.clearAuthData();
                    console.log(`Account ${this.id} (${this.name}) session invalid (${statusCode}), cleared auth. Fresh start attempt ${this.freshStartFailures}...`);
                    this.retryCount = 0;
                    setTimeout(() => this.start(), 10000);
                    return;
                }

                if (isRestartRequired) {
                    // Transient server restart — reconnect silently
                    console.log(`Account ${this.id} (${this.name}) restart required, reconnecting...`);
                    this.status = 'connecting';
                    setTimeout(() => this.initialize(), 2000);
                    return;
                }

                // Generic network / timeout error — exponential backoff, NO notification.
                // If max retries reached: show as disconnected in UI, user clicks reconnect button.
                this.retryCount++;
                if (this.retryCount >= WhatsAppSession.MAX_RETRIES) {
                    console.log(`Account ${this.id} (${this.name}) max retries reached. Stopping silently (auth preserved). User can click reconnect.`);
                    return;
                }
                // Exponential backoff: 3s → 6s → 12s → 24s → 48s (max 60s)
                const delay = Math.min(3000 * Math.pow(2, this.retryCount - 1), 60000);
                console.log(`Account ${this.id} (${this.name}) reconnecting in ${Math.round(delay / 1000)}s (attempt ${this.retryCount})...`);
                this.status = 'connecting';
                setTimeout(() => this.initialize(), delay);

            } else if (connection === 'open') {
                this.connectedJid = this.sock?.user?.id?.replace(/:.*@/, '@');
                console.log(`Account ${this.id} (${this.name}) connected successfully`);
                this.status = 'connected';
                this.qr = undefined;
                this.retryCount = 0;
                this.freshStartFailures = 0;
                this.permanentDisconnectSent = false; // Reset so future disconnects can notify again
                setTimeout(() => { try { this.onConnected?.(); } catch {} }, 5000);
            }
        });

        this.sock.ev.on('creds.update', saveCreds);

        this.sock.ev.on('messages.upsert', async ({ messages: msgs }) => {
            for (const msg of msgs) {
                if (!msg.message || msg.key.fromMe) continue;
                const senderJid = msg.key.remoteJid;
                if (!senderJid) continue;

                const contacts: VCardContact[] = [];

                const contactMsg = msg.message.contactMessage;
                if (contactMsg?.vcard) {
                    const parsed = this.parseVCard(contactMsg.vcard);
                    if (parsed) contacts.push(parsed);
                }

                const contactArray = msg.message.contactsArrayMessage?.contacts;
                if (contactArray) {
                    for (const c of contactArray) {
                        if (c.vcard) {
                            const parsed = this.parseVCard(c.vcard);
                            if (parsed) contacts.push(parsed);
                        }
                    }
                }

                if (contacts.length > 0 && this.onContactsReceived) {
                    this.onContactsReceived(this.id, senderJid, contacts);
                }
            }
        });
    }

    private parseVCard(vcard: string): VCardContact | null {
        try {
            const nameMatch = vcard.match(/FN[;:](.+)/i);
            if (!nameMatch) return null;
            const name = nameMatch[1].trim();
            if (!name) return null;

            const telMatches = vcard.match(/TEL[^:]*:([^\r\n]+)/gi);
            if (!telMatches || telMatches.length === 0) return null;
            
            let phoneNumber = '';
            for (const match of telMatches) {
                const telVal = match.replace(/TEL[^:]*:/i, '').replace(/[\s\-\(\)]/g, '').trim();
                if (telVal && telVal.length >= 7) {
                    phoneNumber = telVal;
                    if (telVal.startsWith('+')) break;
                }
            }
            if (!phoneNumber) return null;
            return { name, phoneNumber };
        } catch {
            return null;
        }
    }

    async clearAuthData() {
        if (this.clearStateFn) {
            await this.clearStateFn().catch(err => {
                console.error(`Failed to clear auth data for account ${this.id}:`, err);
            });
        }
    }

    resetRetryCounters() {
        this.retryCount = 0;
        this.freshStartFailures = 0;
    }

    async softDisconnect() {
        try {
            if (this.sock) {
                this.sock.ev.removeAllListeners('connection.update');
                this.sock.ev.removeAllListeners('creds.update');
                this.sock.ev.removeAllListeners('messages.upsert');
                try { this.sock.end(undefined); } catch {}
            }
        } catch (err) {
            console.error(`Error soft-disconnecting account ${this.id}:`, err);
        }
        this.sock = undefined;
        this.qr = undefined;
        this.status = 'disconnected';
    }

    async disconnect() {
        try {
            if (this.sock) {
                this.sock.ev.removeAllListeners('connection.update');
                this.sock.ev.removeAllListeners('creds.update');
                this.sock.ev.removeAllListeners('messages.upsert');
                try { await this.sock.logout(); } catch {}
                try { this.sock.end(undefined); } catch {}
            }
        } catch (err) {
            console.error(`Error disconnecting account ${this.id}:`, err);
        }
        this.sock = undefined;
        this.qr = undefined;
        this.status = 'disconnected';
    }

    async destroy() {
        await this.disconnect();
        this.clearAuthData();
    }

    async sendMessage(phone: string, message: string, mediaUrl?: string, mediaBuffer?: Buffer) {
        if (!this.sock || this.status !== 'connected') {
            throw new Error('WhatsApp not connected');
        }

        const formattedPhone = (() => {
            const n = phone.replace(/[^0-9]/g, '');
            if (!n.startsWith('0')) return n;
            const len = n.length;
            const p2 = n.slice(0, 2);
            const local = n.slice(1);
            if (len === 10) {
                if (p2 === '05') return '966' + local; // Saudi Arabia 🇸🇦
                if (p2 === '07') return '962' + local; // Jordan       🇯🇴
                if (p2 === '09') return '963' + local; // Syria        🇸🇾
                if (p2 === '03') return '961' + local; // Lebanon      🇱🇧
                if (p2 === '06') return '971' + local; // UAE          🇦🇪
                if (p2 === '08') return '970' + local; // Palestine    🇵🇸
                if (p2 === '02') return '967' + local; // Yemen        🇾🇪
            }
            if (len === 11) {
                if (p2 === '01') return '20'  + local; // Egypt        🇪🇬
                if (p2 === '07') return '964' + local; // Iraq         🇮🇶
                if (p2 === '06') return '213' + local; // Algeria      🇩🇿
                if (p2 === '05') return '212' + local; // Morocco      🇲🇦
            }
            if (len === 9) {
                if (p2 === '09') return '216' + local; // Tunisia      🇹🇳
                if (p2 === '03') return '961' + local; // Lebanon      🇱🇧
                if (p2 === '07') return '967' + local; // Yemen        🇾🇪
            }
            return local; // strip leading 0 at minimum
        })();
        const jid = `${formattedPhone}@s.whatsapp.net`;

        try {
            if (mediaUrl) {
                const isVideo = mediaUrl.match(/\.(mp4|mov|avi|mkv)$/i) || mediaUrl.endsWith('/video');
                const isImage = mediaUrl.match(/\.(jpg|jpeg|png|gif|webp)$/i) || mediaUrl.endsWith('/image');
                
                let mediaSource: { url: string } | Buffer = mediaBuffer || { url: mediaUrl };
                if (!mediaBuffer && (mediaUrl.startsWith('/uploads/') || mediaUrl.startsWith('/api/media/file/'))) {
                    const port = process.env.PORT || '5000';
                    mediaSource = { url: `http://localhost:${port}${mediaUrl}` };
                }

                if (isVideo) {
                    await this.sock.sendMessage(jid, { 
                        video: mediaSource, 
                        caption: message,
                        gifPlayback: false 
                    });
                } else if (isImage) {
                    await this.sock.sendMessage(jid, { 
                        image: mediaSource, 
                        caption: message 
                    });
                } else {
                    await this.sock.sendMessage(jid, { text: message });
                }
            } else {
                await this.sock.sendMessage(jid, { text: message });
            }
            return true;
        } catch (error) {
            console.error(`Error sending message from account ${this.id}:`, error);
            throw error;
        }
    }

    getStatus() {
        const phoneNumber = this.connectedJid?.replace('@s.whatsapp.net', '') || undefined;
        return {
            id: this.id,
            name: this.name,
            status: this.status,
            qr: this.qr,
            phoneNumber,
        };
    }
}

export function randomBetween(min: number, max: number): number {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

type QueueTask = {
    phone: string;
    message: string;
    mediaUrl?: string;
    mediaBuffer?: Buffer;
    resolve: (value: boolean) => void;
    reject: (error: Error) => void;
};

export class WhatsAppManager {
    private sessions: Map<number, WhatsAppSession> = new Map();
    private sessionUserMap: Map<number, number> = new Map();
    private systemQueue: QueueTask[] = [];
    private urgentQueue: QueueTask[] = [];
    private systemQueueProcessing = false;
    private systemLastSentAt = 0;
    private userQueues: Map<number, { queue: QueueTask[]; processing: boolean }> = new Map();
    private userLastSentAt: Map<number, number> = new Map();
    static SYSTEM_DELAY_MIN = 45000;
    static SYSTEM_DELAY_MAX = 45000;
    static GREEN_API_DELAY_MIN = 45000;
    static GREEN_API_DELAY_MAX = 60000;
    static USER_DELAY_MIN = 30000;
    static USER_DELAY_MAX = 30000;

    /** Called by admin send-delay settings to update delays at runtime */
    static setSystemInviteDelay(seconds: number) {
      const ms = Math.max(5000, seconds * 1000);
      WhatsAppManager.SYSTEM_DELAY_MIN = ms;
      WhatsAppManager.SYSTEM_DELAY_MAX = ms;
      WhatsAppManager.GREEN_API_DELAY_MIN = ms;
      WhatsAppManager.GREEN_API_DELAY_MAX = ms;
    }
    onContactsReceived?: OnContactsReceived;
    onSystemConnected?: () => void;

    addSession(id: number, name: string, userId?: number): WhatsAppSession {
        if (this.sessions.has(id)) {
            return this.sessions.get(id)!;
        }
        const session = new WhatsAppSession(id, name);
        session.onContactsReceived = this.onContactsReceived;
        if (userId === 0) {
            session.onConnected = () => this.onSystemConnected?.();
        }
        this.sessions.set(id, session);
        if (userId !== undefined) this.sessionUserMap.set(id, userId);
        return session;
    }

    getSession(id: number): WhatsAppSession | undefined {
        return this.sessions.get(id);
    }

    getSessionUserId(id: number): number | undefined {
        return this.sessionUserMap.get(id);
    }

    getAllSessions(): WhatsAppSession[] {
        return Array.from(this.sessions.values());
    }

    getSystemSession(): WhatsAppSession | undefined {
        return this.getAllSessions().find(s => 
            s.status === 'connected' && this.sessionUserMap.get(s.id) === 0
        );
    }

    getSystemSessionAny(): WhatsAppSession | undefined {
        return this.getAllSessions().find(s => this.sessionUserMap.get(s.id) === 0);
    }

    getConnectedSession(): WhatsAppSession | undefined {
        const system = this.getSystemSession();
        if (system) return system;
        return this.getAllSessions().find(s => s.status === 'connected');
    }

    getConnectedSessionForUser(userId: number): WhatsAppSession | undefined {
        return this.getAllSessions().find(s => 
            s.status === 'connected' && this.sessionUserMap.get(s.id) === userId
        );
    }

    async sendViaSystem(phone: string, message: string, mediaUrl?: string, urgent = false): Promise<boolean> {
        const session = this.getSystemSession();
        if (!session) throw new Error('رقم المشروع غير متصل');
        
        return new Promise((resolve, reject) => {
            const task = { phone, message, mediaUrl, resolve, reject };
            if (urgent) {
                this.urgentQueue.push(task);
            } else {
                this.systemQueue.push(task);
            }
            this.processSystemQueue();
        });
    }

    private async processSystemQueue() {
        if (this.systemQueueProcessing) return;
        this.systemQueueProcessing = true;

        while (this.systemQueue.length > 0 || this.urgentQueue.length > 0) {
            // Drain urgent queue first — no inter-message delay, don't reset systemLastSentAt
            while (this.urgentQueue.length > 0) {
                const task = this.urgentQueue.shift()!;
                await this.executeQueueTask(task, false);
            }

            if (this.systemQueue.length === 0) break;

            // Normal task: enforce 60s minimum delay, but wake early if urgent arrives
            const elapsed = Date.now() - this.systemLastSentAt;
            const minDelay = WhatsAppManager.SYSTEM_DELAY_MIN;
            if (this.systemLastSentAt > 0 && elapsed < minDelay) {
                const waitEnd = Date.now() + (minDelay - elapsed);
                while (Date.now() < waitEnd && this.urgentQueue.length === 0) {
                    await new Promise(r => setTimeout(r, Math.min(500, waitEnd - Date.now())));
                }
                // If urgent arrived, loop back to process it before this normal task
                if (this.urgentQueue.length > 0) continue;
            }

            const task = this.systemQueue.shift()!;
            await this.executeQueueTask(task, true);
        }

        this.systemQueueProcessing = false;
    }

    private async executeQueueTask(task: QueueTask, updateTimer: boolean) {
        if (updateTimer) this.systemLastSentAt = Date.now();

        const session = this.getSystemSession();
        if (!session) {
            task.reject(new Error('رقم المشروع غير متصل'));
            return;
        }

        const sendWithTimeout = Promise.race([
            session.sendMessage(task.phone, task.message, task.mediaUrl),
            new Promise<never>((_, reject) =>
                setTimeout(() => reject(new Error('send timeout after 30s')), 30000)
            ),
        ]);

        try {
            await sendWithTimeout;
            task.resolve(true);
        } catch (err) {
            task.reject(err as Error);
        }
    }

    async sendViaUserAccount(accountId: number, phone: string, message: string, mediaUrl?: string, mediaBuffer?: Buffer): Promise<boolean> {
        const session = this.getSession(accountId);
        if (!session || session.status !== 'connected') throw new Error('حساب واتساب غير متصل');

        if (!this.userQueues.has(accountId)) {
            this.userQueues.set(accountId, { queue: [], processing: false });
        }
        const bucket = this.userQueues.get(accountId)!;

        return new Promise((resolve, reject) => {
            bucket.queue.push({ phone, message, mediaUrl, mediaBuffer, resolve, reject });
            this.processUserQueue(accountId);
        });
    }

    private async processUserQueue(accountId: number) {
        const bucket = this.userQueues.get(accountId);
        if (!bucket || bucket.processing) return;
        bucket.processing = true;

        while (bucket.queue.length > 0) {
            const task = bucket.queue.shift()!;
            const session = this.getSession(accountId);
            if (!session || session.status !== 'connected') {
                task.reject(new Error('حساب واتساب غير متصل'));
                continue;
            }

            const targetDelay = randomBetween(WhatsAppManager.USER_DELAY_MIN, WhatsAppManager.USER_DELAY_MAX);
            const lastSent = this.userLastSentAt.get(accountId) || 0;
            const elapsed = Date.now() - lastSent;
            if (lastSent > 0 && elapsed < targetDelay) {
                await new Promise(r => setTimeout(r, targetDelay - elapsed));
            }

            try {
                await session.sendMessage(task.phone, task.message, task.mediaUrl, task.mediaBuffer);
                this.userLastSentAt.set(accountId, Date.now());
                task.resolve(true);
            } catch (err) {
                task.reject(err as Error);
            }
        }

        bucket.processing = false;
    }

    getSystemDelay() { return WhatsAppManager.SYSTEM_DELAY_MAX; }
    getUserDelay() { return WhatsAppManager.USER_DELAY_MAX; }

    async removeSession(id: number) {
        const session = this.sessions.get(id);
        if (session) {
            await session.destroy();
            this.sessions.delete(id);
            this.sessionUserMap.delete(id);
        }
    }

    async reconnectSession(id: number) {
        const session = this.sessions.get(id);
        if (session) {
            await session.softDisconnect();
            await session.clearAuthData(); // must finish before start() reads from DB
            session.resetRetryCounters();
            await session.start();
        }
    }

    getAllStatuses() {
        return this.getAllSessions().map(s => s.getStatus());
    }
}

export const waManager = new WhatsAppManager();
