import Link from 'next/link';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { getAdminNotesLabel } from '@/lib/admin-notes-lib';
import styles from './header-notification-bell.module.css';
import { coloredToast } from '@/lib/toast';
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
import NotificationsNoneOutlinedIcon from '@mui/icons-material/NotificationsNoneOutlined';
import CheckOutlinedIcon from '@mui/icons-material/CheckOutlined';

const POLL_MS = 20_000;
const FRESH_MS = 5 * 60 * 1000;
const FRESH_KEY = 'aurumlab.headerBell.freshOrders';
const CLICKED_KEY = 'aurumlab.headerBell.clickedOrders';
const CLEARED_KEY = 'aurumlab.headerBell.clearedCompleted';
const SWIPE_MS = 320;
const SWIPE_GAP_MS = 180;

type StampMap = Record<string, number>;

function readStamps(key: string): StampMap {
    try {
        const raw = JSON.parse(localStorage.getItem(key) || '{}');
        if (!raw || typeof raw !== 'object') return {};
        const now = Date.now();
        const next: StampMap = {};
        for (const [id, at] of Object.entries(raw as StampMap)) {
            const time = Number(at);
            if (id && Number.isFinite(time) && now - time < FRESH_MS) next[id] = time;
        }
        return next;
    } catch {
        return {};
    }
}

function writeStamps(key: string, map: StampMap) {
    try {
        localStorage.setItem(key, JSON.stringify(map));
    } catch {
        // Penyimpanan browser penuh atau diblokir.
    }
}

function readCleared(): StampMap {
    try {
        const raw = JSON.parse(localStorage.getItem(CLEARED_KEY) || '{}');
        if (!raw || typeof raw !== 'object') return {};
        const next: StampMap = {};
        for (const [id, at] of Object.entries(raw as StampMap)) {
            const time = Number(at);
            if (id && Number.isFinite(time)) next[id] = time;
        }
        return next;
    } catch {
        return {};
    }
}

function isClearedCompleted(orderId: string, at: number, cleared: StampMap) {
    const stamp = cleared[orderId];
    return Boolean(stamp && stamp >= at);
}

function isFreshOrder(orderId: string, freshAt: StampMap, clickedAt: StampMap, now: number) {
    const at = freshAt[orderId];
    if (!at || now - at >= FRESH_MS) return false;
    const clicked = clickedAt[orderId];
    return !(clicked && clicked >= at);
}

type BellOrder = {
    order_id: string;
    is_preorder: boolean;
    admin_notes: string;
};

type CompletedNote = {
    order_id: string;
    at: number;
};

function mergeCompleted(prev: CompletedNote[], incoming: CompletedNote[]) {
    const next = new Map(prev.map((row) => [row.order_id, row]));
    for (const row of incoming) {
        const existing = next.get(row.order_id);
        next.set(row.order_id, {
            order_id: row.order_id,
            at: existing ? Math.min(existing.at, row.at) : row.at,
        });
    }
    return [...next.values()].sort((a, b) => b.at - a.at);
}

function parseCompleted(rows: unknown): CompletedNote[] {
    if (!Array.isArray(rows)) return [];
    return rows
        .map((row: { order_id?: string; at?: number }) => ({
            order_id: String(row?.order_id || '').trim(),
            at: Number(row?.at) || Date.now(),
        }))
        .filter((row) => row.order_id);
}

function formatCount(value: number) {
    return new Intl.NumberFormat('id-ID').format(Number(value) || 0);
}

const HeaderNotificationBell = () => {
    const [open, setOpen] = useState(false);
    const [orders, setOrders] = useState<BellOrder[]>([]);
    const [processingTotal, setProcessingTotal] = useState(0);
    const [completed, setCompleted] = useState<CompletedNote[]>([]);
    const [clearedAt, setClearedAt] = useState<StampMap>({});
    const [swipingIds, setSwipingIds] = useState<string[]>([]);
    const wrapRef = useRef<HTMLDivElement>(null);
    const openRef = useRef(false);
    const [freshAt, setFreshAt] = useState<StampMap>({});
    const [clickedAt, setClickedAt] = useState<StampMap>({});
    const [now, setNow] = useState(() => Date.now());
    const [ringing, setRinging] = useState(false);
    const ringTimer = useRef(0);
    const noteFreshRef = useRef<(rows: { order_id: string; at: number }[], ring: boolean) => void>(() => { });
    const noteCompletedRef = useRef<(rows: CompletedNote[], ring: boolean) => void>(() => { });
    const completedRef = useRef<CompletedNote[]>([]);
    const clearedRef = useRef<StampMap>({});
    const swipingRef = useRef<string[]>([]);
    completedRef.current = completed;
    clearedRef.current = clearedAt;
    swipingRef.current = swipingIds;

    const ringBell = () => {
        setRinging(true);
        window.clearTimeout(ringTimer.current);
        ringTimer.current = window.setTimeout(() => setRinging(false), 2100);
    };

    const freshAtRef = useRef(freshAt);
    freshAtRef.current = freshAt;

    const noteFresh = (rows: { order_id: string; at: number }[], ring: boolean) => {
        const incoming = rows
            .map((row) => ({ order_id: String(row?.order_id || '').trim(), at: Number(row?.at) || Date.now() }))
            .filter((row) => row.order_id);
        if (!incoming.length) return;
        if (ring && incoming.some((row) => !freshAtRef.current[row.order_id])) ringBell();
        setFreshAt((prev) => {
            const next = { ...prev };
            for (const row of incoming) {
                next[row.order_id] = next[row.order_id] ? Math.min(next[row.order_id], row.at) : row.at;
            }
            const nowMs = Date.now();
            const kept: StampMap = {};
            for (const [id, at] of Object.entries(next)) {
                if (nowMs - at < FRESH_MS) kept[id] = at;
            }
            writeStamps(FRESH_KEY, kept);
            return kept;
        });
    };
    noteFreshRef.current = noteFresh;

    const dismissFresh = (orderId: string) => {
        setClickedAt((prev) => {
            const next = { ...readStamps(CLICKED_KEY), ...prev, [orderId]: Date.now() };
            const nowMs = Date.now();
            const kept: StampMap = {};
            for (const [id, at] of Object.entries(next)) {
                if (nowMs - at < FRESH_MS) kept[id] = at;
            }
            writeStamps(CLICKED_KEY, kept);
            return kept;
        });
    };

    const commitClear = (ids: string[]) => {
        const unique = [...new Set(ids.map((id) => id.trim()).filter(Boolean))];
        if (!unique.length) return;
        const nowMs = Date.now();
        setClearedAt((prev) => {
            const next = { ...prev };
            for (const id of unique) next[id] = nowMs;
            writeStamps(CLEARED_KEY, next);
            return next;
        });
        swipingRef.current = [];
        setSwipingIds([]);
    };
    const commitClearRef = useRef(commitClear);
    commitClearRef.current = commitClear;

    const noteCompleted = (rows: CompletedNote[], ring: boolean) => {
        const incoming = parseCompleted(rows);
        if (!incoming.length) return;
        const known = new Set(completedRef.current.map((row) => row.order_id));
        if (ring && incoming.some((row) => !known.has(row.order_id) && !isClearedCompleted(row.order_id, row.at, clearedRef.current))) ringBell();
        setCompleted((prev) => mergeCompleted(prev, incoming));
    };
    noteCompletedRef.current = noteCompleted;

    const applyOrders = useCallback((list: BellOrder[], nextProcessingTotal: number) => {
        setOrders(list);
        setProcessingTotal(nextProcessingTotal);
    }, []);

    useEffect(() => {
        setFreshAt(readStamps(FRESH_KEY));
        setClickedAt(readStamps(CLICKED_KEY));
        setClearedAt(readCleared());
        const clock = window.setInterval(() => setNow(Date.now()), 15000);
        return () => {
            window.clearInterval(clock);
            window.clearTimeout(ringTimer.current);
        };
    }, []);

    useEffect(() => {
        if (!swipingIds.length) return;
        const wait = (swipingIds.length - 1) * SWIPE_GAP_MS + SWIPE_MS + 80;
        const timer = window.setTimeout(() => {
            if (swipingRef.current.length) commitClearRef.current(swipingRef.current);
        }, wait);
        return () => window.clearTimeout(timer);
    }, [swipingIds]);

    useEffect(() => {
        let pollTimer = 0;
        let retryTimer = 0;
        let refreshTimer = 0;
        let source: EventSource | null = null;
        let stopped = false;
        let requestId = 0;
        let retryMs = 3000;

        const fetchOrders = async () => {
            if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
            const id = ++requestId;
            try {
                const apiUrl = process.env.API_URL || '';
                const res = await fetch(`${apiUrl}api/dashboard/bell/orders`, {
                    method: 'GET',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include',
                });
                const json = await res.json();
                if (!res.ok || stopped || id !== requestId) return;
                const list: BellOrder[] = Array.isArray(json?.orders)
                    ? json.orders
                        .map((row: { order_id?: string; is_preorder?: boolean; admin_notes?: string }) => ({
                            order_id: String(row?.order_id || '').trim(),
                            is_preorder: row?.is_preorder === true,
                            admin_notes: String(row?.admin_notes || '').trim(),
                        }))
                        .filter((row: BellOrder) => row.order_id)
                    : [];
                applyOrders(list, Number(json?.processing_total ?? json?.total) || list.length);
                const serverCompleted = parseCompleted(json?.completed);
                setCompleted(serverCompleted);
                setClearedAt((prev) => {
                    const serverIds = new Set(serverCompleted.map((row) => row.order_id));
                    const kept: StampMap = {};
                    for (const [id, at] of Object.entries(prev)) {
                        if (serverIds.has(id)) kept[id] = at;
                    }
                    writeStamps(CLEARED_KEY, kept);
                    return kept;
                });
                if (Array.isArray(json?.fresh)) {
                    noteFreshRef.current(
                        json.fresh.map((row: { order_id?: string; at?: number }) => ({
                            order_id: String(row?.order_id || ''),
                            at: Number(row?.at) || Date.now(),
                        })),
                        false,
                    );
                }
            } catch {
                // Percobaan berikutnya mengambil ulang.
            }
        };

        const startPoll = () => {
            if (pollTimer || stopped) return;
            pollTimer = window.setInterval(() => {
                void fetchOrders();
            }, POLL_MS);
        };

        const stopPoll = () => {
            window.clearInterval(pollTimer);
            pollTimer = 0;
        };

        const scheduleRefresh = () => {
            window.clearTimeout(refreshTimer);
            refreshTimer = window.setTimeout(() => {
                void fetchOrders();
            }, 150);
        };

        const connect = () => {
            if (stopped || typeof EventSource === 'undefined') {
                startPoll();
                return;
            }
            const apiUrl = process.env.API_URL || '';
            const stream = new EventSource(`${apiUrl}api/dashboard/bell/stream`, { withCredentials: true });
            source = stream;
            let streamClosed = false;

            const fallBack = () => {
                if (streamClosed || stopped) return;
                streamClosed = true;
                stream.close();
                if (source === stream) source = null;
                startPoll();
                if (retryTimer) return;
                retryTimer = window.setTimeout(() => {
                    retryTimer = 0;
                    connect();
                }, retryMs);
                retryMs = Math.min(retryMs + 2000, 15000);
            };

            source.addEventListener('ready', () => {
                stopPoll();
                retryMs = 3000;
                void fetchOrders();
            });
            source.addEventListener('refresh', () => {
                scheduleRefresh();
            });
            source.addEventListener('processing', (event) => {
                try {
                    const data = JSON.parse((event as MessageEvent).data);
                    const orderId = String(data?.order_id || '').trim();
                    if (!orderId) return;
                    noteFreshRef.current([{ order_id: orderId, at: Number(data?.at) || Date.now() }], true);
                } catch {
                    // Abaikan sinyal yang bukan JSON.
                }
            });
            source.addEventListener('completed', (event) => {
                try {
                    const data = JSON.parse((event as MessageEvent).data);
                    noteCompletedRef.current(
                        [{ order_id: String(data?.order_id || ''), at: Number(data?.at) || Date.now() }],
                        true,
                    );
                } catch {
                    // Abaikan sinyal yang bukan JSON.
                }
            });
            source.addEventListener('indelivery', (event) => {
                try {
                    const data = JSON.parse((event as MessageEvent).data);
                    const orderId = String(data?.order_id || '').trim();
                    if (orderId) coloredToast('success', `Order #${orderId} in Delivery`);
                } catch {
                    // Abaikan sinyal yang bukan JSON.
                }
            });
            source.addEventListener('fallback', fallBack);
            source.onerror = fallBack;
        };

        void fetchOrders();
        connect();

        const onVisible = () => {
            if (document.visibilityState === 'visible') void fetchOrders();
        };
        document.addEventListener('visibilitychange', onVisible);

        return () => {
            stopped = true;
            source?.close();
            window.clearInterval(pollTimer);
            window.clearTimeout(retryTimer);
            window.clearTimeout(refreshTimer);
            document.removeEventListener('visibilitychange', onVisible);
        };
    }, [applyOrders]);

    useEffect(() => {
        if (!open) return;
        const onPointerDown = (event: MouseEvent) => {
            if (!wrapRef.current?.contains(event.target as Node)) {
                if (swipingRef.current.length) commitClearRef.current(swipingRef.current);
                openRef.current = false;
                setOpen(false);
            }
        };
        document.addEventListener('mousedown', onPointerDown);
        return () => document.removeEventListener('mousedown', onPointerDown);
    }, [open]);

    const closePanel = () => {
        if (swipingRef.current.length) commitClear(swipingRef.current);
        openRef.current = false;
        setOpen(false);
    };

    const openPanel = () => {
        openRef.current = true;
        setOpen(true);
    };

    const onToggle = () => {
        if (openRef.current) {
            closePanel();
            return;
        }
        openPanel();
    };

    const preorders = useMemo(() => orders.filter((order) => order.is_preorder), [orders]);
    const completedRows = useMemo(
        () => completed.filter((row) => !isClearedCompleted(row.order_id, row.at, clearedAt)).sort((a, b) => b.at - a.at),
        [completed, clearedAt],
    );
    const badgeCount = processingTotal + completedRows.length;

    const startClearCompleted = () => {
        if (swipingRef.current.length || !completedRows.length) return;
        const ids = completedRows.map((row) => row.order_id);
        swipingRef.current = ids;
        setSwipingIds(ids);
    };
    const freshOrder = (orderId: string) => isFreshOrder(orderId, freshAt, clickedAt, now);

    return (
        <div className={`${styles.wrap} ltr:mr-1.5 rtl:ml-1.5`} ref={wrapRef}>
            <button type="button" className={`${styles.bell}`} aria-label="Notifikasi" aria-expanded={open} onClick={onToggle}>
                <div className={`${ringing ? styles.ringing : ''}`}>
                    <NotificationsNoneOutlinedIcon />
                </div>
                {badgeCount > 0 ? <span className={`${styles.dot}`}>{badgeCount}</span> : null}
            </button>

            {open ? (
                <div className={`${styles.panel} shadow-lg bg-white dark:bg-black mt-0 `}>
                    <div className={`${styles.panelHeader} p-4 w-full bg-blue-200 text-md font-bold rounded-tl-xl rounded-tr-xl`}>
                        Notifications
                    </div>
                    <div className={`${styles.listCard} overflow-y-auto scrollbar-thin `}>
                        {orders.length === 0 && completedRows.length === 0 ? <div className={styles.empty}>Tidak ada pesanan Processing</div> : null}
                        {completedRows.map((order, index) => (
                            <Link
                                key={`completed-${order.order_id}`}
                                href={`/orders/edit/${order.order_id}`}
                                className={`${styles.orderRow} ${styles.completedRow} ${swipingIds.includes(order.order_id) ? styles.swiping : ''} border-b border-gray-200 dark:border-gray-800`}
                                style={swipingIds.includes(order.order_id) ? { animationDelay: `${index * SWIPE_GAP_MS}ms` } : undefined}
                                onAnimationEnd={(event) => {
                                    if (event.target !== event.currentTarget) return;
                                    if (order.order_id !== swipingIds[swipingIds.length - 1]) return;
                                    commitClear(swipingIds);
                                }}
                                onClick={closePanel}
                            >
                                <span aria-hidden="true"><CheckOutlinedIcon /></span>
                                <div className={styles.listRow}>
                                    <span className={styles.orderText}>Order completed</span>
                                    <span className={`${styles.orderId} w-[140px]`}>#{order.order_id}</span>
                                </div>
                            </Link>
                        ))}
                        {orders
                            .filter((order) => !order.is_preorder)
                            .map((order) => (
                                <Link
                                    key={order.order_id}
                                    href={`/orders/edit/${order.order_id}`}
                                    className={`${styles.orderRow} border-b border-gray-200 dark:border-gray-800`}
                                    onClick={() => {
                                        dismissFresh(order.order_id);
                                        closePanel();
                                    }}
                                >
                                    <LocalShippingOutlinedIcon />
                                    <div className={styles.listRow}>
                                        <span className={`${styles.orderText} dark:text-slate-500`} title={`Processing Customer on Order #${order.order_id}`}>
                                            Waiting for Shipping
                                        </span>
                                        <span className={`${styles.orderId} w-[140px] dark:text-gray-200`}>#{order.order_id}</span>
                                        {freshOrder(order.order_id) ? (
                                            <span className="relative flex size-2 w-[20px] justify-items-end">
                                                <span className="absolute inline-flex h-[8px] w-[8px] animate-ping rounded-full bg-[#e7515a] opacity-75"></span>
                                                <span className="relative inline-flex size-[8px] rounded-full bg-[#e7515a]"></span>
                                            </span>
                                        ) : null}
                                    </div>
                                </Link>
                            ))}
                        {processingTotal > orders.length ? <div className={styles.extra}>+{formatCount(processingTotal - orders.length)} pesanan lain</div> : null}

                        {preorders.length > 0 ? (
                            <div className={styles.preorderCard}>
                                {preorders.map((order) => (
                                    <Link
                                        key={`preorder-${order.order_id}`}
                                        href={`/orders/edit/${order.order_id}`}
                                        className={`${styles.preorderRow} border-b border-gray-200 dark:border-gray-800`}
                                        onClick={() => {
                                            dismissFresh(order.order_id);
                                            closePanel();
                                        }}
                                    >
                                        <div className={styles.prestatcol}>
                                            <div className={styles.prestatRow}>
                                                <LocalShippingOutlinedIcon />
                                                <div className={styles.listRow}>
                                                    <span className={`${styles.orderText} dark:text-slate-500`} title={`Waiting for Shipping `}>
                                                        Waiting for Shipping
                                                    </span>
                                                    <span className={`${styles.orderId} dark:text-gray-200`} title={`#${order.order_id}`}>
                                                        #{order.order_id}
                                                    </span>
                                                </div>
                                                {freshOrder(order.order_id) ? <span className={styles.freshDot} aria-label="Baru masuk Processing" /> : null}
                                            </div>
                                            <div className={styles.statRow}>
                                                <span className={styles.preorderStatus}>Pre-order</span>
                                                <span className={styles.preorderNotes}>{getAdminNotesLabel(order.admin_notes)}</span>
                                            </div>
                                        </div>
                                    </Link>
                                ))}
                            </div>
                        ) : null}
                    </div>
                    {completedRows.length > 0 && swipingIds.length === 0 ? (
                        <button type="button" className={styles.clearCompleted} onClick={startClearCompleted}>
                            Clear completed
                        </button>
                    ) : null}
                </div>
            ) : null}
        </div>
    );
};

export default HeaderNotificationBell;
