import React, { createContext, useContext, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from './AuthContext';
import { api, buildWsUrl } from '../lib/api';

const NotificationContext = createContext(null);

const LOGO_URL = "/logo.png";

const DING_DATA_URL = "data:audio/wav;base64,UklGRjQEAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YRAEAACAh4qFf3p3foaJg3xzc3eDjY+Jf3Roa3WIlpiNeWNYWmqClqGfimxOQEFXdpW0u6N+VTUcGCxNebTW3rd3OBABBQ0gP3eqztKpekgZBQQRJ0J0nrPCs5JxQRoLECRDc6XL2cKbeUYbBwsfNl+VxNbBmnZHHwYJHTRakcfczKB7VC0SCRkqUYHA5+TBl3JIIAcMHzZflcfTwJ52SR4HCBYqUYO95efGm3VLJg0LHzVcksbazaJ8VS4SCRkqUYHB6OXCmHRIIggMHzZflcfTwJ91Sh4HCBYqUYO95+fGm3VKJg0KHTNakcfczKB7VC0SCRkqUYHA6OXBmHRIIQcMHzZflcfTwJ91Sh4HCBYqUYO85+fGm3VKJg4LHzVcksbazaJ8VS4SCRkqUYHB6OXCmHRIIggMHzZflcfTwJ91Sh4HCBYqUYO95+fGm3VKJg0KHTNakcfczKB7VC0SCRkqUYHA6OXBmHRIIQcMHzZflcfTwJ91Sh4HCBYqUYO85+fGm3VKJg4LHzVcksbazaJ8VS4SCRkqUYHB6OXCmHRIIggMHzZflcfTwJ91Sh4HCBYqUYO95+fGm3VKJg0KHTNakcfczKB7VC0SCRkqUYHA6OXBmHRIIQcMHzZflcfTwJ91Sh4HCBYqUYO85+fGm3VKJg4LHzVcksbazaJ8VS4SCRkqUYHB6OXCmHRIIggMHzZflcfTwJ91Sh4HCBYqUYO95+fGm3VKJg0KHTNakcfczKB7VC0SCRkqUYHA6OXBmHRIIQcMHzZflcfTwJ91Sh4HCBYqUYO85+fGm3VKJg4LHzVcksbazaJ8VS4SCRkqUYHB6OXCmHRIIggMHzZflcfTwJ91Sh4=";

const STATUS_ICONS = { en_attente: 'wait', en_cours: 'play', cloture: 'check' }; // eslint-disable-line no-unused-vars

export function NotificationProvider({ children }) {
    const { user, token } = useAuth();
    const navigate = useNavigate();
    const wsRef = useRef(null);
    const audioRef = useRef(null);
    const navigateRef = useRef(navigate);

    // Keep navigate ref up-to-date so notification onclick has latest navigate function
    useEffect(() => { navigateRef.current = navigate; }, [navigate]);

    useEffect(() => {
        if (!audioRef.current) {
            audioRef.current = new Audio(DING_DATA_URL);
            audioRef.current.volume = 0.6;
        }
    }, []);

    useEffect(() => {
        if (!user) return;
        if (!('Notification' in window)) return;
        if (Notification.permission === 'default') {
            Notification.requestPermission().catch(() => {});
        }
    }, [user]);

    const showSystemNotification = (title, body, tag, folderId) => {
        if (typeof window === 'undefined' || !('Notification' in window)) return false;
        if (Notification.permission !== 'granted') return false;
        // Use the Service Worker's showNotification API — c'est le SEUL moyen
        // d'avoir une vraie notif Windows/Edge native (en bas à droite,
        // visible même onglet en arrière-plan). L'ancienne API `new Notification()`
        // est rendue en toast in-page par Edge moderne.
        try {
            const data = { url: folderId ? `/folders/${folderId}` : '/', folderId };
            (navigator.serviceWorker?.ready || Promise.reject())
                .then((reg) => reg.showNotification(title, {
                    body,
                    icon: LOGO_URL,
                    badge: LOGO_URL,
                    tag,
                    requireInteraction: false,
                    silent: false,
                    data,
                }))
                .catch(() => {
                    // Fallback : new Notification() — peut ne pas marcher sur Edge
                    try {
                        const n = new Notification(title, {
                            body, icon: LOGO_URL, badge: LOGO_URL, tag,
                            requireInteraction: false, silent: false,
                        });
                        n.onclick = (ev) => {
                            ev.preventDefault();
                            try { window.focus(); } catch (_) {}
                            if (folderId) {
                                try { navigateRef.current(`/folders/${folderId}`); }
                                catch (_) { try { window.location.assign(`/folders/${folderId}`); } catch (_) {} }
                            }
                            n.close();
                        };
                    } catch (_) {}
                });
            return true;
        } catch (e) {
            return false;
        }
    };

    const playDing = () => {
        try {
            if (audioRef.current) {
                audioRef.current.currentTime = 0;
                audioRef.current.play().catch(() => {});
            }
        } catch (_) {}
    };

    // Reusable manual-message renderer (used by WS push AND by polling fallback)
    const showManualMessage = (m, markRead = true) => {
        playDing();
        showSystemNotification(m.title, `${m.body}\n— ${m.from}`, `manual-${m.id}`);
        if (markRead) {
            api.post(`/notifications/${m.id}/mark-read`).catch(() => {});
        }
    };

    useEffect(() => {
        if (!token || !user) {
            if (wsRef.current) {
                wsRef.current.close();
                wsRef.current = null;
            }
            return;
        }
        let stopped = false;
        let retry = 0;

        const connect = () => {
            try {
                const ws = new WebSocket(buildWsUrl(token));
                wsRef.current = ws;
                ws.onopen = () => { retry = 0; };
                ws.onmessage = (ev) => {
                    try {
                        const data = JSON.parse(ev.data);
                        dispatchFolderEvent(data);
                    } catch (e) { /* ignore */ }
                };
                ws.onclose = () => {
                    if (stopped) return;
                    retry += 1;
                    setTimeout(connect, Math.min(1000 * retry, 10000));
                };
                ws.onerror = () => { try { ws.close(); } catch (_) {} };
            } catch (e) {
                if (!stopped) setTimeout(connect, 3000);
            }
        };
        connect();

        return () => {
            stopped = true;
            if (wsRef.current) wsRef.current.close();
        };
    }, [token, user]); // eslint-disable-line react-hooks/exhaustive-deps

    // Reusable folder-event dispatcher (used by WS push AND polling fallback)
    // Deduplicates by event key so the same event isn't shown twice if both
    // channels happen to deliver it (e.g. WS reconnects while polling runs).
    const seenEventKeys = useRef(new Set());
    const eventKey = (data) => {
        if (!data || !data.type) return null;
        const fid = data.folder?.id ?? '?';
        // `at` is included by backend for all auto events; if missing (older
        // WS payload), fall back to type+folder which is good enough for the
        // common cases where one folder doesn't fire the same event twice in <15s.
        return `${data.type}-${fid}-${data.at || ''}`;
    };

    const dispatchFolderEvent = (data) => {
        if (!data || !data.type) return;
        if (data.type === 'manual_message') {
            // Manual messages have their own dedupe via shown set in polling
            showManualMessage(data.message, true);
            return;
        }
        const key = eventKey(data);
        if (key && seenEventKeys.current.has(key)) return;
        if (key) seenEventKeys.current.add(key);

        if (data.type === 'new_folder') {
            playDing();
            window.dispatchEvent(new CustomEvent('folder-refresh'));
            showSystemNotification(
                `Nouveau dossier · ${data.folder.plate}`,
                `${data.folder.problem}\npar ${data.folder.cariste_name}`,
                `folder-${data.folder.id}`,
                data.folder.id,
            );
        } else if (data.type === 'status_change') {
            playDing();
            window.dispatchEvent(new CustomEvent('folder-refresh'));
            showSystemNotification(
                `Dossier ${data.folder.plate}`,
                data.folder.label,
                `status-${data.folder.id}`,
                data.folder.id,
            );
        } else if (data.type === 'folder_appended') {
            playDing();
            window.dispatchEvent(new CustomEvent('folder-refresh'));
            showSystemNotification(
                `Mise à jour · ${data.folder.plate}`,
                `${data.folder.label}`,
                `appended-${data.folder.id}`,
                data.folder.id,
            );
        }
    };

    // ---- Polling fallback for ALL notifications (manual + folder events) ----
    // Some reverse proxies (Asustor / Cloudflare free / Nginx without WS upgrade)
    // do not forward WebSockets, so `new_folder`, `status_change` and
    // `folder_appended` events would otherwise never reach the UI.
    // We poll /api/notifications/unread (manual messages) + /api/notifications/events-since
    // (folder events) every 10 s as a safety net. WS-delivered events are
    // deduplicated against polling-delivered ones via `seenEventKeys`.
    useEffect(() => {
        if (!token || !user) return;
        let stopped = false;
        const shownManual = new Set();
        // Per-user cursor stored in localStorage so we don't replay old events
        // on page reload (but we DO catch what happened while the tab was closed
        // for up to a few minutes — limited by the cursor age check below).
        const cursorKey = `notif:events-cursor:${user.id}`;
        let cursor = (() => {
            try {
                const v = localStorage.getItem(cursorKey);
                if (!v) return null;
                // Discard cursors older than 30 min to avoid huge replay on long absence
                const t = new Date(v).getTime();
                if (!isFinite(t) || Date.now() - t > 30 * 60 * 1000) return null;
                return v;
            } catch (_) { return null; }
        })();

        const tick = async () => {
            if (stopped) return;
            // 1) Manual messages
            try {
                const r = await api.get('/notifications/unread');
                const items = r.data || [];
                for (const m of items) {
                    if (shownManual.has(m.id)) continue;
                    shownManual.add(m.id);
                    showManualMessage(m, true);
                }
            } catch (_) { /* silent */ }
            // 2) Folder events since last cursor
            try {
                const r = await api.get('/notifications/events-since', { params: { since: cursor || undefined } });
                const evts = r.data?.events || [];
                for (const ev of evts) {
                    dispatchFolderEvent(ev);
                }
                if (r.data?.now) {
                    cursor = r.data.now;
                    try { localStorage.setItem(cursorKey, cursor); } catch (_) {}
                }
            } catch (_) { /* silent */ }
        };

        // Run once immediately (catch missed messages on reload), then every 10s
        tick();
        const id = setInterval(tick, 10000);

        return () => { stopped = true; clearInterval(id); };
    }, [token, user]); // eslint-disable-line react-hooks/exhaustive-deps

    return (
        <NotificationContext.Provider value={{}}>
            {children}
        </NotificationContext.Provider>
    );
}

export function useNotifications() {
    return useContext(NotificationContext);
}
