"use client";

import { useEffect, useState, useCallback, useRef } from "react";
import { io, Socket } from "socket.io-client";

const SOCKET_URL =
  process.env.NEXT_PUBLIC_SOCKET_URL || "http://localhost:3007";
const API_BASE =
  process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:3007/api";

export interface Notification {
  id: number;
  type: string;
  title: string;
  body: string;
  target_role_ids: number[];
  target_user_ids: number[] | null;
  reference_id: number | null;
  reference_type: string | null;
  is_urgent: boolean;
  requires_action: boolean;
  is_read: boolean;
  read_by_user_id: number | null;
  created_at: string;
}
// ── ADD at the top, after existing constants ─────────────────
function urlBase64ToUint8Array(base64String: string) {
  const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
  const rawData = atob(base64);
  return Uint8Array.from([...rawData].map((c) => c.charCodeAt(0)));
}

async function registerPushSubscription(token: string) {
  try {
    if (!("serviceWorker" in navigator) || !("PushManager" in window)) return;

    const permission = await Notification.requestPermission();
    if (permission !== "granted") return;

    const reg = await navigator.serviceWorker.register("/sw.js");
    await navigator.serviceWorker.ready;

    // Get VAPID key from your backend
    const keyRes = await fetch(
      `${API_BASE}/notifications/push/vapid-public-key`,
      {
        headers: { Authorization: `Bearer ${token}` },
      },
    );
    if (!keyRes.ok) return;
    const { key } = await keyRes.json();

    // Subscribe
    const subscription = await reg.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(key),
    });

    // Send subscription to backend
    await fetch(`${API_BASE}/notifications/push/subscribe`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`,
      },
      body: JSON.stringify(subscription),
    });

    console.log("✅ Push notifications registered");
  } catch (err) {
    console.warn("Push registration failed:", err);
  }
}

// ✅ NEW — Gmail-style toast type
export interface ToastNotification extends Notification {
  toastId: string;
}

// ── Singleton AudioContext — created once, reused forever ────
let _audioCtx: AudioContext | null = null;
let _lastPlayTime = 0; // debounce guard

function getAudioContext(): AudioContext {
  if (!_audioCtx || _audioCtx.state === "closed") {
    _audioCtx = new (
      window.AudioContext || (window as any).webkitAudioContext
    )();
  }
  return _audioCtx;
}

function playNotificationSound() {
  try {
    // ── Debounce — ignore if played less than 1 second ago ───
    const now = Date.now();
    if (now - _lastPlayTime < 1000) return;
    _lastPlayTime = now;

    const ctx = getAudioContext();

    // Resume context if browser suspended it (autoplay policy)
    if (ctx.state === "suspended") {
      ctx.resume();
    }

    const t = ctx.currentTime;

    const masterGain = ctx.createGain();
    masterGain.gain.setValueAtTime(0.55, t);
    masterGain.connect(ctx.destination);

    const filter = ctx.createBiquadFilter();
    filter.type = "lowpass";
    filter.frequency.setValueAtTime(4800, t);
    filter.Q.setValueAtTime(0.4, t);
    filter.connect(masterGain);

    const fmBell = (
      carrierFreq: number,
      startTime: number,
      duration: number,
      amplitude: number,
    ) => {
      const modulatorFreq = carrierFreq * 2.756;
      const modulationIndex = 8;

      const modOsc = ctx.createOscillator();
      modOsc.type = "sine";
      modOsc.frequency.setValueAtTime(modulatorFreq, startTime);

      const modGain = ctx.createGain();
      modGain.gain.setValueAtTime(0.0001, startTime);
      modGain.gain.exponentialRampToValueAtTime(
        modulatorFreq * modulationIndex,
        startTime + 0.005,
      );
      modGain.gain.exponentialRampToValueAtTime(
        modulatorFreq * 0.3,
        startTime + duration * 0.25,
      );
      modGain.gain.exponentialRampToValueAtTime(0.0001, startTime + duration);

      modOsc.connect(modGain);

      const carrierOsc = ctx.createOscillator();
      carrierOsc.type = "sine";
      carrierOsc.frequency.setValueAtTime(carrierFreq, startTime);
      modGain.connect(carrierOsc.frequency);

      const carrierGain = ctx.createGain();
      carrierGain.gain.setValueAtTime(0.0001, startTime);
      carrierGain.gain.exponentialRampToValueAtTime(
        amplitude,
        startTime + 0.004,
      );
      carrierGain.gain.exponentialRampToValueAtTime(
        amplitude * 0.4,
        startTime + duration * 0.15,
      );
      carrierGain.gain.exponentialRampToValueAtTime(
        0.0001,
        startTime + duration,
      );

      carrierOsc.connect(carrierGain);
      carrierGain.connect(filter);

      modOsc.start(startTime);
      modOsc.stop(startTime + duration + 0.05);
      carrierOsc.start(startTime);
      carrierOsc.stop(startTime + duration + 0.05);
    };

    fmBell(860, t, 1.8, 0.9);
    fmBell(860 * 2.756, t + 0.002, 0.9, 0.12);
  } catch (e) {
    console.warn("Notification sound failed:", e);
  }
}
export function useNotifications() {
  const [notifications, setNotifications] = useState<Notification[]>([]);
  const [unreadCount, setUnreadCount] = useState(0);
  const [connected, setConnected] = useState(false);
  const socketRef = useRef<Socket | null>(null);

  // ✅ NEW — toast queue
  const [toasts, setToasts] = useState<ToastNotification[]>([]);

  // ✅ NEW — dismiss a toast
  const dismissToast = useCallback((toastId: string) => {
    setToasts((prev) => prev.filter((t) => t.toastId !== toastId));
  }, []);

  // Get token from localStorage
  // ✅ FIX — check `access_token` first (the app convention), then fall
  // back to `token`, so the socket always finds the JWT.
  const getToken = () => {
    if (typeof window === "undefined") return null;
    return (
      localStorage.getItem("access_token") || localStorage.getItem("token")
    );
  };

  // Fetch existing notifications from REST API
  const fetchNotifications = useCallback(async () => {
    const token = getToken();
    if (!token) return;
    try {
      const res = await fetch(`${API_BASE}/notifications`, {
        headers: { Authorization: `Bearer ${token}` },
      });
      if (res.ok) {
        const data = await res.json();
        setNotifications(data);
      }
    } catch (err) {
      console.error("Failed to fetch notifications", err);
    }
  }, []);

  // Fetch unread count
  const fetchUnreadCount = useCallback(async () => {
    const token = getToken();
    if (!token) return;
    try {
      const res = await fetch(`${API_BASE}/notifications/unread-count`, {
        headers: { Authorization: `Bearer ${token}` },
      });
      if (res.ok) {
        const data = await res.json();
        setUnreadCount(data.count);
      }
    } catch (err) {
      console.error("Failed to fetch unread count", err);
    }
  }, []);

  // Mark single notification as read
  const markRead = useCallback(async (id: number) => {
    const token = getToken();
    if (!token) return;
    try {
      await fetch(`${API_BASE}/notifications/${id}/read`, {
        method: "PATCH",
        headers: { Authorization: `Bearer ${token}` },
      });
      setNotifications((prev) =>
        prev.map((n) => (n.id === id ? { ...n, is_read: true } : n)),
      );
      setUnreadCount((prev) => Math.max(0, prev - 1));
    } catch (err) {
      console.error("Failed to mark notification as read", err);
    }
  }, []);

  // Mark all as read
  const markAllRead = useCallback(async () => {
    const token = getToken();
    if (!token) return;
    try {
      await fetch(`${API_BASE}/notifications/mark-all-read`, {
        method: "PATCH",
        headers: { Authorization: `Bearer ${token}` },
      });
      setNotifications((prev) => prev.map((n) => ({ ...n, is_read: true })));
      setUnreadCount(0);
    } catch (err) {
      console.error("Failed to mark all as read", err);
    }
  }, []);

  // Connect socket
  useEffect(() => {
    const token = getToken();
    if (!token) return;

    // ✅ FIX — token may carry the user id as `sub` OR `id`. Accept either,
    // so a valid token is never rejected before the socket connects.
    try {
      const payload = JSON.parse(atob(token.split(".")[1]));
      if (!payload?.sub && !payload?.id) return;
    } catch {
      return;
    }

    fetchNotifications();
    fetchUnreadCount();
    registerPushSubscription(token);

    // ✅ FIX — connect to the '/notifications' NAMESPACE.
    // The gateway now runs on namespace '/notifications' (same '/socket.io'
    // path) to avoid colliding with ChatGateway. The namespace is appended
    // to the URL here; `path` stays '/socket.io' so the Apache proxy is
    // unaffected. Chat continues to use its own namespace — untouched.
    const socket = io(`${SOCKET_URL}/notifications`, {
      transports: ["websocket", "polling"],
      auth: { token: token },
      extraHeaders: { Authorization: `Bearer ${token}` },
      withCredentials: true,
    });

    socketRef.current = socket;

    socket.on("connect", () => {
      console.log("✅ [useNotifications] socket connected");
      setConnected(true);
    });
    socket.on("disconnect", () => setConnected(false));
    socket.on("connect_error", (err) => {
      console.warn("[useNotifications] socket connect_error:", err.message);
    });

    // ✅ UPDATED — play sound + show toast on new notification
    const handleNotification = (notification: Notification) => {
      console.log("🔔 [useNotifications] notification received:", notification);
      setNotifications((prev) => [notification, ...prev]);
      setUnreadCount((prev) => prev + 1);

      // 🔊 Play sound
      playNotificationSound();

      // 🍞 Show toast — auto dismiss after 6 seconds
      const toastId = `toast-${Date.now()}-${notification.id}`;
      const toast: ToastNotification = { ...notification, toastId };
      setToasts((prev) => [toast, ...prev.slice(0, 2)]); // max 3 toasts
      setTimeout(() => {
        setToasts((prev) => prev.filter((t) => t.toastId !== toastId));
      }, 6000);
    };

    socket.on("notification", handleNotification);
    socket.on("notification:urgent", handleNotification);
    socket.on("notification:action", handleNotification);

    return () => {
      socket.off("notification", handleNotification);
      socket.off("notification:urgent", handleNotification);
      socket.off("notification:action", handleNotification);
      socket.disconnect();
    };
  }, [fetchNotifications, fetchUnreadCount]);

  return {
    notifications,
    unreadCount,
    connected,
    markRead,
    markAllRead,
    refetch: fetchNotifications,
    // ✅ NEW
    toasts,
    dismissToast,
  };
}
