// Backups für Preis- und Tag-Änderungen (Preislisten-Abgleich und manuelle Preisanpassung).
// Vor jeder Änderung werden die alten Werte der betroffenen Produkte vom Server als eigene JSON-Datei abgelegt
// (Route /api/price-backups, Ordner auf dem Server, automatische Löschung nach der Aufbewahrungsfrist, Standard 90 Tage).
import { shopifyQuery } from './shopify-client';

/** Eine geänderte Variante. Wertepaare sind [vorher, nachher]; Beträge als Zahlenstring. */
export interface BackupItem {
  v: string; // Varianten-ID (nur die Nummer)
  p: string; // Produkt-ID (nur die Nummer)
  s: string; // SKU
  t: string; // Bezeichnung (gekürzt)
  c?: string; // Währung des Einkaufspreises
  ek?: [string, string];
  vk?: [string, string];
}

/** Geänderte Tags eines Produkts: a = hinzugefügt, r = entfernt. */
export interface BackupTagChange {
  p: string;
  s: string; // SKU(s) des Produkts
  a: string[];
  r: string[];
}

export interface BackupEntry {
  id: string; // ISO-Zeitstempel der Änderung
  src: 'manual' | 'pricelist' | 'restore';
  n?: string; // Hinweis, z. B. Dateiname der Preisliste
  items: BackupItem[];
  tags: BackupTagChange[];
}

export const gidVariant = (n: string): string => `gid://shopify/ProductVariant/${n}`;
export const gidProduct = (n: string): string => `gid://shopify/Product/${n}`;
export const numId = (gid: string): string => String(gid).split('/').pop() || String(gid);
export const shorten = (s: string, n = 70): string => (s.length > n ? `${s.slice(0, n - 1)}…` : s);

/** Shopify-Abfrage mit Wiederholung bei Drosselung; liefert Daten und Fehler. */
export async function gql(query: string, variables: any, attempts = 4): Promise<{ data: any; errors: any[] }> {
  let last: { data: any; errors: any[] } = { data: null, errors: [] };
  for (let i = 0; i < attempts; i++) {
    try {
      const res: any = await shopifyQuery(query, { variables });
      last = { data: res?.data ?? null, errors: res?.errors || [] };
    } catch (e: any) {
      last = { data: null, errors: [{ message: e?.message || 'Netzwerkfehler' }] };
    }
    const throttled = last.errors.some((e: any) => /throttl|rate limit|timeout|502|503/i.test(String(e?.message || '')));
    if (last.errors.length === 0 || !throttled) return last;
    await new Promise((r) => setTimeout(r, 1000 * (i + 1) * (i + 1)));
  }
  return last;
}

let retentionDays = 90;
/** Aufbewahrungsfrist laut Server (nach dem ersten Laden bekannt). */
export const getRetentionDays = (): number => retentionDays;

async function authHeaders(): Promise<Record<string, string>> {
  const shopify = (window as any).shopify;
  const token = shopify?.idToken ? await shopify.idToken() : '';
  return token ? { Authorization: `Bearer ${token}` } : {};
}

/** Alle Backups, neueste zuerst. Wirft bei Fehlern. */
export async function loadBackups(): Promise<BackupEntry[]> {
  const res = await fetch('/api/price-backups', { headers: { ...(await authHeaders()), Accept: 'application/json' } });
  const body: any = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(body?.error || `Server antwortet mit Status ${res.status}`);
  if (body.retentionDays) retentionDays = Number(body.retentionDays) || retentionDays;
  const entries: BackupEntry[] = Array.isArray(body.entries) ? body.entries : [];
  return entries.sort((a, b) => (a.id < b.id ? 1 : -1));
}

/** Legt einen Backup-Eintrag auf dem Server an. Wirft bei Fehlern – dann darf NICHT weiter geändert werden. */
export async function saveBackupEntry(entry: BackupEntry): Promise<void> {
  if (entry.items.length === 0 && entry.tags.length === 0) return;
  const res = await fetch('/api/price-backups', {
    method: 'POST',
    headers: { ...(await authHeaders()), 'Content-Type': 'application/json' },
    body: JSON.stringify(entry),
  });
  const body: any = await res.json().catch(() => ({}));
  if (!res.ok || !body?.ok) throw new Error(body?.error || `Server antwortet mit Status ${res.status}`);
}

// ---------- Zusammenführen für die Wiederherstellung ----------

export interface RestoreItem {
  v: string;
  p: string;
  s: string;
  t: string;
  c: string;
  ek?: string; // Zielwert (Stand vor der Änderung)
  vk?: string;
}
export interface RestoreTags {
  p: string;
  s: string;
  add: string[]; // wieder hinzuzufügende Tags
  remove: string[]; // wieder zu entfernende Tags
}

/**
 * Berechnet den Stand VOR dem Eintrag `fromId`. Mit `includeLater` werden auch alle späteren Änderungen zurückgenommen
 * (Einträge werden von neu nach alt abgearbeitet, der älteste Vorher-Wert gewinnt).
 */
export function mergeForRestore(
  entries: BackupEntry[],
  fromId: string,
  includeLater: boolean,
): { items: RestoreItem[]; tags: RestoreTags[] } {
  const chosen = entries
    .filter((e) => (includeLater ? e.id >= fromId : e.id === fromId))
    .sort((a, b) => (a.id < b.id ? 1 : -1)); // neu → alt
  const items = new Map<string, RestoreItem>();
  const tags = new Map<string, { p: string; s: string; add: Set<string>; remove: Set<string> }>();
  for (const e of chosen) {
    for (const it of e.items) {
      const cur = items.get(it.v) || { v: it.v, p: it.p, s: it.s, t: it.t, c: it.c || 'EUR' };
      if (it.ek) cur.ek = it.ek[0];
      if (it.vk) cur.vk = it.vk[0];
      if (it.c) cur.c = it.c;
      items.set(it.v, cur);
    }
    for (const tc of e.tags) {
      const cur = tags.get(tc.p) || { p: tc.p, s: tc.s, add: new Set<string>(), remove: new Set<string>() };
      for (const t of tc.a) {
        cur.remove.add(t);
        cur.add.delete(t);
      }
      for (const t of tc.r) {
        cur.add.add(t);
        cur.remove.delete(t);
      }
      tags.set(tc.p, cur);
    }
  }
  return {
    items: [...items.values()].sort((a, b) => a.s.localeCompare(b.s, 'de', { numeric: true })),
    tags: [...tags.values()]
      .map((t) => ({ p: t.p, s: t.s, add: [...t.add], remove: [...t.remove] }))
      .filter((t) => t.add.length || t.remove.length),
  };
}
