import makeWASocket, {
    DisconnectReason,
    isJidBroadcast,
    makeCacheableSignalKeyStore,
    AnyMessageContent,
    fetchLatestBaileysVersion,
} from "baileys";
import * as baileys from "baileys";
import type { ConnectionState, SocketConfig, WASocket, proto, AuthenticationCreds, SignalKeyStore } from "baileys";
import { Store, useSession } from "./store";
import { prisma } from "@/config/database";
import { logger, delay, emitEvent } from "@/utils";
import { WAStatus } from "@/types";
import type { Boom } from "@hapi/boom";
import type { Response } from "express";
import { toDataURL } from "qrcode";
import type { WebSocket as WebSocketType } from "ws";
import env from "@/config/env";

export type Session = WASocket & {
    destroy: () => Promise<void>;
    store: Store;
    waStatus?: WAStatus;
};

type createSessionOptions = {
    sessionId: string;
    res?: Response;
    SSE?: boolean;
    readIncomingMessages?: boolean;
    socketConfig?: SocketConfig;
    usePairingCode?: boolean;
    phoneNumber?: string;
    authDir?: string;
};

class WhatsappService {
    private static sessions = new Map<string, Session>();
    private static retries = new Map<string, number>();
    private static SSEQRGenerations = new Map<string, number>();

    constructor() {
        this.init();
    }

    private async init() {
        const storedSessions = await prisma.session.findMany({
            select: { sessionId: true, data: true },
            where: { id: { startsWith: env.SESSION_CONFIG_ID } },
        });
        for (const { sessionId, data } of storedSessions) {
            const { readIncomingMessages, authDir, ...socketConfig } = JSON.parse(data);
            WhatsappService.createSession({ sessionId, readIncomingMessages, socketConfig, authDir });
        }
    }

    private static updateWaConnection(sessionId: string, waStatus: WAStatus) {
        if (WhatsappService.sessions.has(sessionId)) {
            const _session = WhatsappService.sessions.get(sessionId)!;
            WhatsappService.sessions.set(sessionId, { ..._session, waStatus });
            emitEvent("connection.update", sessionId, { status: waStatus });
        }
    }

    private static shouldReconnect(sessionId: string) {
        let attempts = WhatsappService.retries.get(sessionId) ?? 0;

        if (attempts < env.MAX_RECONNECT_RETRIES) {
            attempts += 1;
            WhatsappService.retries.set(sessionId, attempts);
            return true;
        }
        return false;
    }

    static async createSession(options: createSessionOptions) {
        const { sessionId, res, SSE = false, readIncomingMessages = false, socketConfig, usePairingCode, phoneNumber, authDir } = options;
        const configID = `${env.SESSION_CONFIG_ID}-${sessionId}`;
        let connectionState: Partial<ConnectionState> = { connection: "close" };

        const destroy = async (logout = true) => {
            try {
                await Promise.all([
                    logout && socket.logout(),
                    prisma.chat.deleteMany({ where: { sessionId } }),
                    prisma.contact.deleteMany({ where: { sessionId } }),
                    prisma.message.deleteMany({ where: { sessionId } }),
                    prisma.groupMetadata.deleteMany({ where: { sessionId } }),
                    prisma.session.deleteMany({ where: { sessionId } }),
                ]);
                logger.info({ session: sessionId }, "Session destroyed");
            } catch (e) {
                logger.error(e, "An error occurred during session destroy");
            } finally {
                WhatsappService.sessions.delete(sessionId);
                WhatsappService.updateWaConnection(sessionId, WAStatus.Disconected);
            }
        };

        const handleConnectionClose = () => {
            const error = connectionState.lastDisconnect?.error as Boom;
            const code = error?.output?.statusCode;
            const restartRequired = code === DisconnectReason.restartRequired;
            const doNotReconnect = !WhatsappService.shouldReconnect(sessionId);

            // Log detailed error information
            logger.error({
                error: error?.message || 'Unknown error',
                errorCode: code,
                session: sessionId,
                fullError: error
            }, 'Connection closed with error');

            WhatsappService.updateWaConnection(sessionId, WAStatus.Disconected);

            if (code === DisconnectReason.loggedOut || doNotReconnect) {
                if (res) {
                    !SSE &&
                        !res.headersSent &&
                        res.status(500).json({
                            error: "Unable to create session",
                            reason: error?.message || 'Connection failed',
                            code
                        });
                    res.end();
                }
                destroy(doNotReconnect);
                return;
            }

            const delay = restartRequired ? 2000 : env.RECONNECT_INTERVAL; // Add 2s delay even for restart
            logger.info(
                { attempts: WhatsappService.retries.get(sessionId) ?? 1, sessionId, delay },
                "Reconnecting...",
            );
            setTimeout(
                () => WhatsappService.createSession(options),
                delay,
            );
        };

        const handleNormalConnectionUpdate = async () => {
            // Skip QR code generation completely if using pairing code mode
            if (usePairingCode) {
                logger.info({ sessionId }, 'Skipping QR code generation - using pairing code mode');
                return;
            }

            if (connectionState.qr?.length) {
                if (res && !res.headersSent) {
                    try {
                        const qr = await toDataURL(connectionState.qr);
                        WhatsappService.updateWaConnection(sessionId, WAStatus.WaitQrcodeAuth);
                        emitEvent("qrcode.updated", sessionId, { qr });
                        res.status(200).json({ qr });
                        return;
                    } catch (e) {
                        logger.error(e, "An error occurred during QR generation");
                        emitEvent(
                            "qrcode.updated",
                            sessionId,
                            undefined,
                            "error",
                            `Unable to generate QR code: ${e.message}`,
                        );
                        res.status(500).json({ error: "Unable to generate QR" });
                    }
                }
                // Only destroy if not using pairing code AND credentials are not registered
                if (!usePairingCode && !socket.authState.creds.registered) {
                    logger.info({ sessionId }, 'Destroying session - QR timeout and no credentials');
                    destroy();
                }
            }
        };

        const handleSSEConnectionUpdate = async () => {
            let qr: string | undefined = undefined;
            if (connectionState.qr?.length) {
                try {
                    WhatsappService.updateWaConnection(sessionId, WAStatus.WaitQrcodeAuth);
                    qr = await toDataURL(connectionState.qr);
                } catch (e) {
                    logger.error(e, "An error occurred during QR generation");
                    emitEvent(
                        "qrcode.updated",
                        sessionId,
                        undefined,
                        "error",
                        `Unable to generate QR code: ${e.message}`,
                    );
                }
            }

            const currentGenerations = WhatsappService.SSEQRGenerations.get(sessionId) ?? 0;
            if (
                !res ||
                res.writableEnded ||
                (qr && currentGenerations >= env.SSE_MAX_QR_GENERATION)
            ) {
                res && !res.writableEnded && res.end();
                destroy();
                return;
            }

            const data = { ...connectionState, qr };
            if (qr) {
                WhatsappService.SSEQRGenerations.set(sessionId, currentGenerations + 1);
                emitEvent("qrcode.updated", sessionId, { qr });
            }
            res.write(`data: ${JSON.stringify(data)}\n\n`);
        };

        const handleConnectionUpdate = SSE
            ? handleSSEConnectionUpdate
            : handleNormalConnectionUpdate;
        let state: any;
        let saveCreds: () => Promise<void>;
        if (authDir) {
            const fsAuth = await (baileys as any).useMultiFileAuthState(authDir);
            state = fsAuth.state;
            saveCreds = fsAuth.saveCreds;
        } else {
            const dbAuth = await useSession(sessionId);
            state = dbAuth.state;
            saveCreds = dbAuth.saveCreds;
        }
        // Fetch latest version like the working example
        const { version, isLatest } = await fetchLatestBaileysVersion();
        logger.info({ version, isLatest }, `using WA v${version.join('.')}, isLatest: ${isLatest}`);
		logger.info({ version, isLatest }, `using WA v${version.join('.')}, isLatest: ${isLatest}`);

		// Create a wrapper to block app state sync keys
	const originalKeyStore = makeCacheableSignalKeyStore(state.keys, logger);
	const keyStoreWithoutAppState = {
		...originalKeyStore,
		get: async (type: any, ids: any[]) => {
			// Block app-state-sync-key requests to prevent app state synchronization
			if (type === 'app-state-sync-key') {
				logger.info({ sessionId, type, count: ids.length }, 'BLOCKED app-state-sync-key request');
				return {};
			}
			return originalKeyStore.get(type as any, ids);
		}
	};

		const socket = makeWASocket({
			version,
			logger,
			printQRInTerminal: !usePairingCode, // Only print QR in terminal if not using pairing code
			auth: {
				creds: state.creds,
				keys: keyStoreWithoutAppState as any,
			},
			generateHighQualityLinkPreview: true,
			shouldIgnoreJid: (jid) => isJidBroadcast(jid),
			getMessage: async (key) => undefined, // DISABLE message loading to prevent sync
			shouldSyncHistoryMessage: () => false, // DISABLE history sync completely
			syncFullHistory: false,
			markOnlineOnConnect: false, // Don't mark online immediately
			browser: ['Chrome (Linux)', '', ''], // More realistic browser string
			defaultQueryTimeoutMs: 60000, // Increase timeout
			retryRequestDelayMs: 2000, // Slow down retries
			connectTimeoutMs: 60000,
			emitOwnEvents: false, // Don't emit events for own messages
			fireInitQueries: false, // Don't fire initial queries that trigger heavy sync
			...socketConfig,
		});

		// DISABLE Store to avoid bot detection - no message/chat sync
	const store: any = undefined; // new Store(sessionId, socket.ev);

	WhatsappService.sessions.set(sessionId, {
		...socket,
		destroy,
		store,
		waStatus: WAStatus.Unknown,
	});

	// Track if pairing code has been requested - use object to maintain reference
	const pairingState = {
		codeRequested: false,
		codeGenerated: false,
		timestamp: 0
	};

	// Request pairing code after a short delay to let WebSocket initialize
	if (usePairingCode && !socket.authState.creds.registered) {
		setTimeout(async () => {
			if (!pairingState.codeRequested) {
				pairingState.codeRequested = true;
				try {
					if (!phoneNumber) {
						throw new Error('phoneNumber is required when usePairingCode is true');
					}
					logger.info({ sessionId }, `Requesting pairing code for ${phoneNumber}...`);
					const code = await socket.requestPairingCode(phoneNumber);
					pairingState.codeGenerated = true;
					pairingState.timestamp = Date.now();
					logger.info({ sessionId }, `Pairing code generated: ${code}. Valid for 60 seconds. Enter it now in WhatsApp!`);
					if (res && !res.headersSent) {
						res.status(200).json({
							sessionId,
							pairingCode: code,
							expiresIn: 60,
							message: 'Enter this code in WhatsApp > Linked Devices > Link a Device > Link with phone number instead'
						});
					}
					// Don't end response - let connection.update handler handle it when auth completes
				} catch (e: any) {
					logger.error(e, 'Failed to request pairing code');
					if (res && !res.headersSent) {
						res.status(500).json({ error: 'Unable to request pairing code', reason: e?.message });
						res.end();
					}
				}
			}
		}, 3000); // Wait 3 seconds for WebSocket to be ready
	}

	// This line is moved up with the other event handlers
	// Cast event emitter to handle type compatibility
	const eventEmitter = socket.ev as any;

	// BLOCK ALL sync events to prevent WhatsApp bot detection
	eventEmitter.on("messaging-history.set", () => {
		logger.info({ sessionId }, 'BLOCKED messaging-history.set');
	});
	eventEmitter.on("chats.upsert", () => {
		logger.info({ sessionId }, 'BLOCKED chats.upsert');
	});
	eventEmitter.on("chats.update", () => {
		logger.info({ sessionId }, 'BLOCKED chats.update');
	});
	eventEmitter.on("contacts.upsert", () => {
		logger.info({ sessionId }, 'BLOCKED contacts.upsert');
	});
	eventEmitter.on("messages.upsert", () => {
		logger.info({ sessionId }, 'BLOCKED messages.upsert');
	});
	eventEmitter.on("messages.update", () => {
		logger.info({ sessionId }, 'BLOCKED messages.update');
	});

	eventEmitter.on("creds.update", saveCreds);
	eventEmitter.on("connection.update", async (update) => {
		connectionState = update;
		const { connection } = update;

		// Log connection state for debugging
		logger.info({
			sessionId,
			connection,
			usePairingCode,
			pairingCodeGenerated: pairingState.codeGenerated,
			isNewLogin: update.isNewLogin
		}, 'Connection update');

			if (connection === "open") {
				WhatsappService.updateWaConnection(
					sessionId,
					update.isNewLogin ? WAStatus.Authenticated : WAStatus.Connected,
				);
				WhatsappService.retries.delete(sessionId);
				WhatsappService.SSEQRGenerations.delete(sessionId);

				// Log successful authentication with pairing code
				if (usePairingCode && pairingState.codeGenerated) {
					const elapsed = Date.now() - pairingState.timestamp;
					logger.info({ sessionId, elapsedMs: elapsed }, 'Successfully authenticated with pairing code!');
				}
			}
			if (connection === "close") handleConnectionClose();
			if (connection === "connecting")
				WhatsappService.updateWaConnection(sessionId, WAStatus.PullingWAData);
			handleConnectionUpdate();
	});

	// readIncomingMessages is DISABLED - all message events are blocked above to prevent sync

		await prisma.session.upsert({
			create: {
				id: configID,
				sessionId,
				data: JSON.stringify({ readIncomingMessages, authDir, ...socketConfig }),
			},
			update: {},
			where: { sessionId_id: { id: configID, sessionId } },
		});
	}

	static getSessionStatus(session: Session): string {
		if (!session) return 'NOT_FOUND';
		if (session.waStatus === WAStatus.Connected) return 'CONNECTED';
		if (session.waStatus === WAStatus.WaitQrcodeAuth) return 'WAIT_QR_CODE';
		return 'PENDING';
	}

	static sessionExists(sessionId: string): boolean {
		const session = WhatsappService.getSession(sessionId);
		return session !== undefined && WhatsappService.getSessionStatus(session) === 'CONNECTED';
	}

	static listSessions() {
		return Array.from(WhatsappService.sessions.entries()).map(([id, session]) => ({
			id,
			status: WhatsappService.getSessionStatus(session),
		}));
	}

	static getSession(sessionId: string) {
		return WhatsappService.sessions.get(sessionId);
	}

	static async deleteSession(sessionId: string) {
		WhatsappService.sessions.get(sessionId)?.destroy();
	}

	static async jidExists(session: Session, jid: string, type: "group" | "number" = "number") {
		try {
			if (type === "number") {
				const [result] = await session.onWhatsApp(jid);
				return !!result?.exists;
			}

			const groupMeta = await session.groupMetadata(jid);
			return !!groupMeta.id;
		} catch (e) {
			return Promise.reject(e);
		}
	}
}

export default WhatsappService;
