/**
 * 실시간 매매 시그널 알림 Context
 *
 * - STOMP 수신 시 토스트 스택(미확인)에 추가
 * - 알림 목록·읽음 상태는 localStorage + 서버 trade-signals 이력 병합
 */
import React, {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import type { TradeSignalInfo } from '../components/TradeAlertModal';
import { loadTradeSignals, type TradeSignalDto } from '../utils/backendApi';
import {
  normalizeTradeAlertSoundId,
  playTradeAlertSound,
  type TradeAlertSoundId,
} from '../utils/tradeAlertSound';

const STORAGE_KEY = 'gc_trade_notify_read_v1';
const MAX_HISTORY = 300;
/** 팝업 대기열 최대 건수 (페이지 슬라이딩) */
const MAX_TOAST_QUEUE = 50;

export interface TradeNotificationItem extends TradeSignalInfo {
  /** 클라이언트 고유 키 (market:time:signal) */
  id: string;
  dbId?: number;
  isRead: boolean;
  receivedAt: number;
}

interface TradeNotificationContextValue {
  /** 우측 스택에 표시 중인 미확인 알림 */
  toastNotifications: TradeNotificationItem[];
  /** 전체 이력 (목록 화면) */
  allNotifications: TradeNotificationItem[];
  unreadCount: number;
  addNotification: (signal: TradeSignalInfo & { dbId?: number }) => void;
  dismissToast: (id: string) => void;
  /** 지정 id 알림만 팝업 대기열에서 닫기 (읽음 처리) */
  dismissToasts: (ids: string[]) => void;
  /** 팝업 대기열(toastNotifications) 전체 닫기 — 모든 페이지 포함 */
  dismissAllToasts: () => void;
  markAsRead: (id: string) => void;
  markAllAsRead: () => void;
  refreshHistory: () => Promise<void>;
  detailSignal: TradeSignalInfo | null;
  /** 상세 모달을 화면 중앙에 표시 (알림 목록에서 열 때) */
  detailCentered: boolean;
  openDetail: (item: TradeNotificationItem, options?: { centered?: boolean }) => void;
  closeDetail: () => void;
}

const TradeNotificationContext = createContext<TradeNotificationContextValue | null>(null);

function makeId(signal: Pick<TradeSignalInfo, 'market' | 'candleTime' | 'signalType'>): string {
  return `${signal.market}:${signal.candleTime}:${signal.signalType}`;
}

function dtoToItem(dto: TradeSignalDto, readIds: Set<string>): TradeNotificationItem {
  const id = `${dto.market}:${dto.candleTime}:${dto.signalType}`;
  return {
    id,
    dbId: dto.id,
    market: dto.market,
    signalType: dto.signalType,
    price: dto.price,
    candleTime: dto.candleTime,
    strategyName: dto.strategyName,
    strategyId: dto.strategyId,
    executionType: dto.executionType,
    candleType: dto.candleType,
    isRead: readIds.has(id),
    receivedAt: new Date(dto.createdAt).getTime() || dto.candleTime * 1000,
  };
}

function loadReadIds(): Set<string> {
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) return new Set();
    return new Set(JSON.parse(raw) as string[]);
  } catch {
    return new Set();
  }
}

function saveReadIds(ids: Set<string>) {
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify([...ids]));
  } catch { /* ignore */ }
}

export function useTradeNotification(): TradeNotificationContextValue {
  const ctx = useContext(TradeNotificationContext);
  if (!ctx) {
    throw new Error('useTradeNotification must be used within TradeNotificationProvider');
  }
  return ctx;
}

interface ProviderProps {
  children: React.ReactNode;
  /** 매매 알림 팝업 ON (설정) */
  popupEnabled: boolean;
  /** 매매 알림 사운드 ON (설정) */
  soundEnabled?: boolean;
  /** 알림음 ID */
  soundId?: string;
}

export const TradeNotificationProvider: React.FC<ProviderProps> = ({
  children,
  popupEnabled,
  soundEnabled = true,
  soundId = 'bell',
}) => {
  const alertSoundId = normalizeTradeAlertSoundId(soundId) as TradeAlertSoundId;
  const [readIds, setReadIds] = useState<Set<string>>(() => loadReadIds());
  const [toastNotifications, setToastNotifications] = useState<TradeNotificationItem[]>([]);
  const toastNotificationsRef = useRef(toastNotifications);
  toastNotificationsRef.current = toastNotifications;
  const [allNotifications, setAllNotifications] = useState<TradeNotificationItem[]>([]);
  const [detailSignal, setDetailSignal] = useState<TradeSignalInfo | null>(null);
  const [detailCentered, setDetailCentered] = useState(false);

  const unreadCount = useMemo(
    () => allNotifications.filter(n => !n.isRead).length,
    [allNotifications],
  );

  const refreshHistory = useCallback(async () => {
    try {
      const dtos = await loadTradeSignals();
      const items = dtos.map(d => dtoToItem(d, readIds));

      setAllNotifications(prev => {
        const isBoot = prev.length === 0;
        const newlyArrived = items.filter(
          item => !item.isRead && !prev.some(p => p.id === item.id),
        );

        if (!isBoot && popupEnabled && newlyArrived.length > 0) {
          setToastNotifications(tprev => {
            const map = new Map(tprev.map(t => [t.id, t]));
            for (const n of newlyArrived) map.set(n.id, n);
            return [...map.values()].slice(0, MAX_TOAST_QUEUE);
          });
          if (soundEnabled) {
            playTradeAlertSound(alertSoundId);
          }
        }

        const map = new Map<string, TradeNotificationItem>();
        for (const n of items) map.set(n.id, n);
        for (const n of prev) {
          if (!map.has(n.id)) map.set(n.id, n);
        }
        return [...map.values()]
          .sort((a, b) => b.receivedAt - a.receivedAt)
          .slice(0, MAX_HISTORY);
      });
    } catch (e) {
      console.warn('[TradeNotification] 이력 로드 실패:', e);
    }
  }, [readIds, popupEnabled, soundEnabled, alertSoundId]);

  useEffect(() => {
    void refreshHistory();
  }, [refreshHistory]);

  const markAsRead = useCallback((id: string) => {
    setReadIds(prev => {
      const next = new Set(prev);
      next.add(id);
      saveReadIds(next);
      return next;
    });
    setToastNotifications(prev => prev.filter(n => n.id !== id));
    setAllNotifications(prev =>
      prev.map(n => (n.id === id ? { ...n, isRead: true } : n)),
    );
  }, []);

  const addNotification = useCallback((signal: TradeSignalInfo & { dbId?: number }) => {
    const id = makeId(signal);
    // 실시간 재수신 시 이전에 읽음 처리된 동일 키도 다시 미확인으로
    setReadIds(prev => {
      if (!prev.has(id)) return prev;
      const next = new Set(prev);
      next.delete(id);
      saveReadIds(next);
      return next;
    });
    const item: TradeNotificationItem = {
      ...signal,
      id,
      dbId: signal.dbId,
      candleType: signal.candleType ?? '1m',
      isRead: false,
      receivedAt: Date.now(),
    };

    // 배지(미확인 건수)는 팝업 설정과 무관하게 항상 갱신
    setAllNotifications(prev => {
      const existing = prev.find(n => n.id === id);
      if (existing) {
        return prev.map(n =>
          n.id === id
            ? { ...n, ...item, isRead: false, receivedAt: item.receivedAt }
            : n,
        );
      }
      return [item, ...prev].slice(0, MAX_HISTORY);
    });

    if (soundEnabled) {
      playTradeAlertSound(alertSoundId);
    }

    if (!popupEnabled) return;

    setToastNotifications(prev => {
      const rest = prev.filter(n => n.id !== id);
      return [item, ...rest].slice(0, MAX_TOAST_QUEUE);
    });
  }, [popupEnabled, soundEnabled, alertSoundId]);

  const dismissToast = useCallback((id: string) => {
    markAsRead(id);
  }, [markAsRead]);

  const dismissToasts = useCallback((ids: string[]) => {
    if (ids.length === 0) return;
    const idSet = new Set(ids);
    setReadIds(r => {
      const next = new Set(r);
      ids.forEach(i => next.add(i));
      saveReadIds(next);
      return next;
    });
    setToastNotifications(prev => prev.filter(n => !idSet.has(n.id)));
    setAllNotifications(all =>
      all.map(n => (idSet.has(n.id) ? { ...n, isRead: true } : n)),
    );
  }, []);

  const dismissAllToasts = useCallback(() => {
    const snapshot = toastNotificationsRef.current;
    if (snapshot.length === 0) return;
    const idSet = new Set(snapshot.map(n => n.id));

    setReadIds(r => {
      const next = new Set(r);
      idSet.forEach(i => next.add(i));
      saveReadIds(next);
      return next;
    });
    setToastNotifications([]);
    setAllNotifications(all =>
      all.map(n => (idSet.has(n.id) ? { ...n, isRead: true } : n)),
    );
  }, []);

  const markAllAsRead = useCallback(() => {
    setAllNotifications(prev => {
      const nextRead = new Set(readIds);
      prev.forEach(n => nextRead.add(n.id));
      saveReadIds(nextRead);
      setReadIds(nextRead);
      return prev.map(n => ({ ...n, isRead: true }));
    });
    setToastNotifications([]);
  }, [readIds]);

  const openDetail = useCallback((item: TradeNotificationItem, options?: { centered?: boolean }) => {
    setDetailCentered(options?.centered ?? false);
    setDetailSignal({
      market: item.market,
      signalType: item.signalType,
      price: item.price,
      candleTime: item.candleTime,
      strategyName: item.strategyName,
      strategyId: item.strategyId,
      executionType: item.executionType,
      candleType: item.candleType,
    });
    markAsRead(item.id);
  }, [markAsRead]);

  const closeDetail = useCallback(() => {
    setDetailSignal(null);
    setDetailCentered(false);
  }, []);

  const value: TradeNotificationContextValue = {
    toastNotifications,
    allNotifications,
    unreadCount,
    addNotification,
    dismissToast,
    dismissToasts,
    dismissAllToasts,
    markAsRead,
    markAllAsRead,
    refreshHistory,
    detailSignal,
    detailCentered,
    openDetail,
    closeDetail,
  };

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