import { readLocalJson, writeLocalJson, removeLocalValue } from './safeStorage';
import { getCsrfToken } from '@/lib/csrf';
import { captureCaught } from '@/lib/observability';

const WISHLIST_QUEUE_KEY = 'multideal:wishlist:offline-queue:v1';

export interface WishlistQueueEntry {
  dealId: string;
  desiredSaved: boolean;
  updatedAt: number;
  seq?: number;
}

interface StoredWishlistQueue {
  owner: string;
  entries: WishlistQueueEntry[];
}

function queueEntryKey(entry: WishlistQueueEntry): string {
  return `${entry.dealId}\u0000${entry.seq ?? 0}`;
}

function isQueueEntry(value: unknown): value is WishlistQueueEntry {
  if (!value || typeof value !== 'object') return false;
  const candidate = value as Record<string, unknown>;
  return (
    typeof candidate.dealId === 'string' &&
    typeof candidate.desiredSaved === 'boolean' &&
    typeof candidate.updatedAt === 'number' &&
    (candidate.seq === undefined || typeof candidate.seq === 'number')
  );
}

function isStoredWishlistQueue(value: unknown): value is StoredWishlistQueue {
  if (!value || typeof value !== 'object') return false;
  const candidate = value as Record<string, unknown>;
  return (
    typeof candidate.owner === 'string' &&
    Array.isArray(candidate.entries) &&
    candidate.entries.every(isQueueEntry)
  );
}

export function clearWishlistQueue(): void {
  removeLocalValue(WISHLIST_QUEUE_KEY);
}

function readQueue(owner: string): WishlistQueueEntry[] {
  if (!owner) return [];
  const stored = readLocalJson<StoredWishlistQueue | null>(
    WISHLIST_QUEUE_KEY,
    null,
    (value): value is StoredWishlistQueue | null => value === null || isStoredWishlistQueue(value),
  );
  if (!stored) return [];
  if (stored.owner !== owner) {
    clearWishlistQueue();
    return [];
  }
  return stored.entries;
}

function writeQueue(owner: string, queue: WishlistQueueEntry[]): void {
  if (queue.length === 0) {
    clearWishlistQueue();
    return;
  }
  writeLocalJson(WISHLIST_QUEUE_KEY, { owner, entries: queue });
}

let nextSequence = 0;
let replayInFlight: Promise<void> | null = null;

function allocateSequence(queue: WishlistQueueEntry[]): number {
  const storedMaximum = queue.reduce((maximum, entry) => Math.max(maximum, entry.seq ?? 0), 0);
  nextSequence = Math.max(nextSequence, storedMaximum) + 1;
  return nextSequence;
}

function mergeQueueEntries(entries: WishlistQueueEntry[]): WishlistQueueEntry[] {
  const merged = new Map<string, WishlistQueueEntry>();
  for (const entry of entries) {
    const previous = merged.get(entry.dealId);
    if (!previous || (entry.seq ?? 0) >= (previous.seq ?? 0)) {
      merged.set(entry.dealId, entry);
    }
  }
  return [...merged.values()];
}

export function enqueueWishlistDesiredState(dealId: string, desiredSaved: boolean): void {
  const owner = getCsrfToken();
  if (!owner) return;
  const stored = readQueue(owner);
  const seq = allocateSequence(stored);
  const queue = stored.filter((entry) => entry.dealId !== dealId);
  const updatedAt = Math.max(Date.now(), (queue.at(-1)?.updatedAt ?? 0) + 1);
  queue.push({ dealId, desiredSaved, updatedAt, seq });
  writeQueue(owner, queue);
}

export function getQueuedWishlistState(owner: string): Map<string, boolean> {
  return new Map(readQueue(owner).map((entry) => [entry.dealId, entry.desiredSaved]));
}

async function fetchServerWishlist(): Promise<Set<string>> {
  const res = await fetch('/api/wishlist', {
    method: 'GET',
    headers: { Accept: 'application/json' },
    cache: 'no-store',
  });
  if (!res.ok) throw new Error(`wishlist_get_${res.status}`);
  const json = (await res.json()) as { ok?: boolean; items?: Array<{ dealId: string }> };
  return new Set((json.items ?? []).map((item) => item.dealId));
}

async function toggleServerWishlist(dealId: string): Promise<boolean> {
  const csrf = getCsrfToken();
  const res = await fetch('/api/wishlist/toggle', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf },
    body: JSON.stringify({ dealId, notify: false }),
  });
  if (!res.ok) throw new Error(`wishlist_toggle_${res.status}`);
  const json = (await res.json()) as { saved?: boolean; data?: { saved?: boolean } };
  return json.saved ?? json.data?.saved ?? false;
}

async function replayWishlistQueueInternal(owner: string): Promise<void> {
  const queue = readQueue(owner);
  if (queue.length === 0) return;
  if (typeof navigator !== 'undefined' && navigator.onLine === false) return;

  const snapshotKeys = new Set(queue.map(queueEntryKey));

  const serverSaved = await fetchServerWishlist();
  const pending: WishlistQueueEntry[] = [];

  for (const entry of queue) {
    const alreadyMatches = serverSaved.has(entry.dealId) === entry.desiredSaved;
    if (alreadyMatches) continue;
    try {
      const nextSaved = await toggleServerWishlist(entry.dealId);
      if (nextSaved === entry.desiredSaved) {
        if (nextSaved) serverSaved.add(entry.dealId);
        else serverSaved.delete(entry.dealId);
      } else {
        pending.push(entry);
      }
    } catch (err) {
      captureCaught(err, {
        scope: 'lib.offline.wishlistQueue.replayWishlistQueue',
        severity: 'warning',
      });
      pending.push(entry);
    }
  }

  const currentQueue = readQueue(owner).filter((entry) => !snapshotKeys.has(queueEntryKey(entry)));
  writeQueue(owner, mergeQueueEntries([...pending, ...currentQueue]));
}

export function replayWishlistQueue(owner: string): Promise<void> {
  if (replayInFlight) return replayInFlight;

  const run = replayWishlistQueueInternal(owner);
  const guardedRun = run.finally(() => {
    if (replayInFlight === guardedRun) replayInFlight = null;
  });
  replayInFlight = guardedRun;
  return guardedRun;
}
