import { useEffect, useMemo, useRef, useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";

import { api } from "./api";
import { queryKeys } from "./queryKeys";

/**
 * Mode démo côté SPA — horloge virtuelle + boucle de tick.
 *
 * L'idée : avancer virtualNow LOCALEMENT (smooth via rAF) puis sync
 * périodiquement avec le backend qui fire les tasks dues. Quand un
 * event est déclenché, on auto-pause et on pousse l'email dans une
 * file pour la slide-over.
 *
 * Tout le code démo est isolé ici et dans /components/demo. La prod
 * ne charge ce hook que pour les orgs avec `demoMode = true`.
 */

export type FiredEvent = {
  kind: "relance" | "checkin";
  taskId: string;
  invoiceId: string;
  invoiceNumero: string;
  capturedEmailId: string | null;
  firedAt: string;
};

export type DemoState = {
  demoMode: boolean;
  virtualNow?: string; // ISO
  speedFactor?: number;
  nextEventAt?: string | null;
  inboxCount?: number;
};

export type DemoCapturedEmail = {
  id: string;
  kind: "relance" | "checkin";
  from: { email: string; name: string | null };
  to: { email: string; name: string | null };
  replyTo: string | null;
  subject: string;
  body: string;
  meta: Record<string, unknown>;
  sentAt: string;
};

/**
 * Vitesse : nombre de jours-démo par seconde réelle.
 * 1 = 1 jour-démo / seconde (= 30 jours en 30s, lecture posée pour démo)
 * 2 = 2 jours/s, etc.
 */
export const SPEED_OPTIONS = [1, 2, 5] as const;
export type Speed = (typeof SPEED_OPTIONS)[number];

/**
 * Interval entre 2 syncs backend pendant la lecture, en ms.
 * Trade-off : plus court = plus réactif aux events, plus de charge.
 */
const SYNC_INTERVAL_MS = 250;

export const queryKeysDemo = {
  state: () => ["demo", "state"] as const,
  inbox: () => ["demo", "inbox"] as const,
};

/**
 * Hook principal — query l'état démo. Si demoMode=false, retourne juste
 * le flag. Le composant DemoClock se monte conditionnellement.
 */
export function useDemoState() {
  return useQuery({
    queryKey: queryKeysDemo.state(),
    queryFn: () => api.get<DemoState>("/api/v1/demo/state"),
    staleTime: 0,
    refetchOnWindowFocus: false,
  });
}

export function useDemoInbox(enabled: boolean) {
  return useQuery({
    queryKey: queryKeysDemo.inbox(),
    queryFn: () => api.get<DemoCapturedEmail[]>("/api/v1/demo/inbox"),
    enabled,
    staleTime: 0,
    refetchOnWindowFocus: false,
  });
}

export function useDemoStart() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: () => api.post<DemoState>("/api/v1/demo/start"),
    onSuccess: () => {
      void qc.invalidateQueries({ queryKey: queryKeysDemo.state() });
      void qc.invalidateQueries({ queryKey: queryKeysDemo.inbox() });
      void qc.invalidateQueries({ queryKey: queryKeys.dashboard.all() });
    },
  });
}

export function useDemoEnd() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: () => api.post<{ demoMode: false }>("/api/v1/demo/end"),
    onSuccess: () => {
      void qc.invalidateQueries({ queryKey: queryKeysDemo.state() });
      void qc.invalidateQueries({ queryKey: queryKeys.dashboard.all() });
    },
  });
}

/**
 * Boucle de tick — avance virtualNow localement (rAF) et sync le
 * backend périodiquement. Quand fired events > 0 : auto-pause +
 * push event dans la file.
 *
 * Retourne :
 *  - virtualNow : Date locale, mise à jour à chaque frame
 *  - playing : true si la pastille avance
 *  - speed : 1 | 2 | 5
 *  - play / pause / setSpeed : controls
 *  - pendingEvents : queue des events fired (à dépiler par l'UI quand
 *    l'utilisateur les acquitte)
 *  - acknowledge : retire le 1er event de la file (et quand vide, l'UI
 *    sait qu'on peut reprendre)
 *
 * @param initialVirtualNow ISO string de la backend lors du mount
 * @param enabled false = ne pas démarrer la boucle (pour orgs non-démo)
 */
export function useDemoTick(params: {
  enabled: boolean;
  initialVirtualNow?: string;
}) {
  const { enabled, initialVirtualNow } = params;
  const qc = useQueryClient();

  const [virtualNow, setVirtualNow] = useState<Date>(() =>
    initialVirtualNow ? new Date(initialVirtualNow) : new Date(),
  );
  const [playing, setPlaying] = useState(false);
  const [speed, setSpeed] = useState<Speed>(1);
  const [pendingEvents, setPendingEvents] = useState<FiredEvent[]>([]);

  // Ref pour le scheduler — on évite de recréer la closure à chaque render.
  const lastTickRef = useRef<number>(performance.now());
  const lastSyncRef = useRef<number>(0);
  const virtualNowRef = useRef<Date>(virtualNow);
  virtualNowRef.current = virtualNow;
  const playingRef = useRef(playing);
  playingRef.current = playing;
  const speedRef = useRef(speed);
  speedRef.current = speed;

  // Sync avec le backend.
  const syncWithBackend = async () => {
    const target = virtualNowRef.current.toISOString();
    try {
      const { firedEvents } = await api.post<{
        virtualNow: string;
        firedEvents: FiredEvent[];
      }>("/api/v1/demo/tick", { virtualNow: target });
      if (firedEvents.length > 0) {
        // Auto-pause + push events
        playingRef.current = false;
        setPlaying(false);
        setPendingEvents((prev) => [...prev, ...firedEvents]);
        // Refresh inbox + dashboard pour que l'UI reflète l'état réel
        void qc.invalidateQueries({ queryKey: queryKeysDemo.inbox() });
        void qc.invalidateQueries({ queryKey: queryKeys.dashboard.all() });
      }
    } catch {
      // Erreur réseau / backend down → on pause silencieusement, l'UI
      // reste cohérente, l'utilisateur peut retenter.
      playingRef.current = false;
      setPlaying(false);
    }
  };

  // Boucle rAF
  useEffect(() => {
    if (!enabled) return;
    let raf = 0;
    const loop = (now: number) => {
      raf = requestAnimationFrame(loop);
      if (!playingRef.current) {
        lastTickRef.current = now;
        return;
      }
      const elapsedRealMs = now - lastTickRef.current;
      lastTickRef.current = now;
      // speed = jours-démo par seconde réelle.
      // → 1 ms réelle = (speed / 1000) jour-démo = (speed * 86_400_000 / 1000) ms virtuelles.
      const advanceMs = (speedRef.current * elapsedRealMs * 86_400_000) / 1000;
      const next = new Date(virtualNowRef.current.getTime() + advanceMs);
      virtualNowRef.current = next;
      setVirtualNow(next);

      // Sync avec backend toutes les SYNC_INTERVAL_MS
      if (now - lastSyncRef.current >= SYNC_INTERVAL_MS) {
        lastSyncRef.current = now;
        void syncWithBackend();
      }
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [enabled]);

  const acknowledge = () => {
    setPendingEvents((prev) => prev.slice(1));
  };

  // L'UI considère qu'on peut "play" si on n'a aucun event en attente.
  const canPlay = pendingEvents.length === 0;

  return useMemo(
    () => ({
      virtualNow,
      playing,
      speed,
      pendingEvents,
      canPlay,
      play: () => {
        if (!pendingEvents.length) {
          lastTickRef.current = performance.now();
          setPlaying(true);
        }
      },
      pause: () => setPlaying(false),
      setSpeed: (s: Speed) => setSpeed(s),
      acknowledge,
      // Reset à la valeur backend (utile après /demo/start)
      resetTo: (iso: string) => {
        const d = new Date(iso);
        virtualNowRef.current = d;
        setVirtualNow(d);
      },
    }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [virtualNow, playing, speed, pendingEvents, canPlay],
  );
}
