import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
import type { ReactNode } from 'react';
import { apiFetch, getToken, useAuth } from './auth';

export type NotificationCategoryKey = 'listing' | 'messages' | 'system';

// Powiadomienie w formie z backendu.
export type ServerNotification = {
  id: number;
  category: NotificationCategoryKey;
  icon: string;
  title: string;
  body: string | null;
  link: string | null;
  dedupeKey: string | null;
  read: boolean;
  createdAt: string;
};

// Zdarzenia liczone po stronie klienta (alerty cen, spotkania, ulubione, telefon, dopasowania).
export type NotifyInput = {
  category: NotificationCategoryKey;
  icon?: string;
  title: string;
  body?: string;
  link?: string;
  dedupeKey?: string;
};

// Kształt do renderu (zgodny z istniejącym UI: title/description/timeLabel/dayLabel/category/icon/unread/action).
export type DisplayNotification = ServerNotification & {
  description: string;
  timeLabel: string;
  dayLabel: 'Dzisiaj' | 'Wczoraj';
  unread: boolean;
  action?: 'arrow';
};

type NotificationsContextValue = {
  notifications: ServerNotification[];
  unreadCount: number;
  markRead: (id: number) => void;
  markAllRead: () => void;
  notify: (input: NotifyInput) => void;
  // Kasuje powiadomienia o kluczu zaczynajacym sie od prefiksu - uzywane przy usuwaniu
  // alertu cenowego, zeby nie zostal po nim slad.
  dismissByDedupePrefix: (prefix: string) => void;
  refresh: () => void;
};

const NotificationsContext = createContext<NotificationsContextValue | null>(null);

function pad(value: number): string {
  return value < 10 ? `0${value}` : String(value);
}

// Mapowanie listy z backendu na kształt oczekiwany przez istniejący widok (bez zmiany designu).
export function toDisplayNotifications(list: ServerNotification[]): DisplayNotification[] {
  const now = new Date();
  const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
  const startOfYesterday = startOfToday - 24 * 60 * 60 * 1000;

  return list.map((item) => {
    const created = new Date(item.createdAt);
    const createdMs = created.getTime();
    const time = `${pad(created.getHours())}:${pad(created.getMinutes())}`;
    const isToday = createdMs >= startOfToday;
    const isYesterday = createdMs >= startOfYesterday && createdMs < startOfToday;

    let timeLabel: string;
    if (isToday) {
      timeLabel = time;
    } else if (isYesterday) {
      timeLabel = `Wczoraj, ${time}`;
    } else {
      timeLabel = `${pad(created.getDate())}.${pad(created.getMonth() + 1)}, ${time}`;
    }

    const unread = !item.read;
    return {
      ...item,
      description: item.body ?? '',
      timeLabel,
      // Widok grupuje wyłącznie na "Dzisiaj"/"Wczoraj" - starsze trafiają do "Wczoraj".
      dayLabel: isToday ? 'Dzisiaj' : 'Wczoraj',
      unread,
      action: !unread && item.link ? 'arrow' : undefined,
    };
  });
}

export function NotificationsProvider({ children }: { children: ReactNode }) {
  const { user } = useAuth();
  const [notifications, setNotifications] = useState<ServerNotification[]>([]);
  const sentDedupeKeys = useRef<Set<string>>(new Set());

  const unreadCount = notifications.reduce((count, item) => (item.read ? count : count + 1), 0);

  const refresh = useCallback(() => {
    if (!getToken()) {
      setNotifications([]);
      return;
    }
    apiFetch<ServerNotification[]>('/notifications')
      .then((data) => setNotifications(Array.isArray(data) ? data : []))
      .catch(() => { /* cicho - brak sieci/tokenu */ });
  }, []);

  // Ładowanie listy + realtime (SSE) gdy użytkownik zalogowany. Token w query param (EventSource nie ustawia nagłówków).
  useEffect(() => {
    const token = getToken();
    if (!user || !token) {
      setNotifications([]);
      sentDedupeKeys.current = new Set();
      return;
    }

    refresh();

    const source = new EventSource(`/api/notifications/stream?access_token=${encodeURIComponent(token)}`);
    source.addEventListener('notification', (event) => {
      try {
        const incoming = JSON.parse((event as MessageEvent).data) as ServerNotification;
        setNotifications((current) => (current.some((item) => item.id === incoming.id) ? current : [incoming, ...current]));
      } catch {
        /* ignoruj nieparsowalne zdarzenie */
      }
    });
    // onerror: EventSource sam ponawia połączenie.

    // Zapasowy polling na wypadek zerwanego SSE.
    const pollId = window.setInterval(refresh, 60000);

    return () => {
      source.close();
      window.clearInterval(pollId);
    };
  }, [user, refresh]);

  const markRead = useCallback((id: number) => {
    setNotifications((current) => current.map((item) => (item.id === id ? { ...item, read: true } : item)));
    apiFetch(`/notifications/${id}/read`, { method: 'POST' }).catch(() => {});
  }, []);

  const markAllRead = useCallback(() => {
    setNotifications((current) => current.map((item) => ({ ...item, read: true })));
    apiFetch('/notifications/read-all', { method: 'POST' }).catch(() => {});
  }, []);

  const notify = useCallback((input: NotifyInput) => {
    if (!getToken() || !input.title) {
      return;
    }
    if (input.dedupeKey) {
      if (sentDedupeKeys.current.has(input.dedupeKey)) {
        return;
      }
      sentDedupeKeys.current.add(input.dedupeKey);
    }
    apiFetch<ServerNotification | null>('/notifications', { method: 'POST', body: JSON.stringify(input) })
      .then((created) => {
        if (created && created.id) {
          setNotifications((current) => (current.some((item) => item.id === created.id) ? current : [created, ...current]));
        }
      })
      .catch(() => {});
  }, []);

  const dismissByDedupePrefix = useCallback((prefix: string) => {
    if (!getToken() || !prefix) {
      return;
    }
    // Znikaja od razu z listy, reszte sprzata backend.
    setNotifications((current) => current.filter((item) => !(item.dedupeKey ?? '').startsWith(prefix)));
    // Zdejmujemy tez blokade deduplikacji, inaczej po ponownym dodaniu alertu ta sama
    // zmiana ceny nie wygenerowalaby juz powiadomienia w tej sesji.
    sentDedupeKeys.current.forEach((key) => {
      if (key.startsWith(prefix)) {
        sentDedupeKeys.current.delete(key);
      }
    });
    apiFetch(`/notifications?dedupePrefix=${encodeURIComponent(prefix)}`, { method: 'DELETE' }).catch(() => {});
  }, []);

  return (
    <NotificationsContext.Provider
      value={{ notifications, unreadCount, markRead, markAllRead, notify, dismissByDedupePrefix, refresh }}
    >
      {children}
    </NotificationsContext.Provider>
  );
}

export function useNotifications(): NotificationsContextValue {
  const context = useContext(NotificationsContext);
  if (!context) {
    throw new Error('useNotifications musi być użyte wewnątrz NotificationsProvider');
  }
  return context;
}
