import { useEffect, useState } from 'react';
import {
  loadBackups,
  getRetentionDays,
  saveBackupEntry,
  mergeForRestore,
  gidVariant,
  gidProduct,
  gql,
  type BackupEntry,
  type RestoreItem,
  type RestoreTags,
} from '../lib/price-backup';

// Ansicht „Preis-Backups": Liste der gespeicherten Änderungen und Wiederherstellung des Standes vor einem Zeitpunkt.

type ChangeHandler = (e: any) => void;
const listen = (type: string, handler: ChangeHandler) => (el: HTMLElement | null) => {
  if (!el) return;
  const holder = ((el as any).__handlers ??= {}) as Record<string, ChangeHandler>;
  if (!holder[type]) el.addEventListener(type, (e) => (el as any).__handlers[type](e));
  holder[type] = handler;
};

const SRC_LABEL: Record<string, string> = {
  manual: 'Manuelle Preisanpassung',
  pricelist: 'Preislisten-Abgleich',
  restore: 'Wiederherstellung',
};
const fmtDate = (iso: string): string => {
  const d = new Date(iso);
  return Number.isNaN(d.getTime()) ? iso : d.toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'medium' });
};
const num = (s: string | undefined): number | null => {
  if (s === undefined || s === '') return null;
  const n = Number(String(s).replace(',', '.'));
  return Number.isFinite(n) ? n : null;
};
const same = (a: string | undefined, b: string | undefined): boolean => {
  const x = num(a);
  const y = num(b);
  return x !== null && y !== null && Math.abs(x - y) < 0.005;
};
const has = (tags: string[], t: string): boolean => tags.some((x) => x.trim().toLowerCase() === t.trim().toLowerCase());

interface Current {
  variants: Record<string, { ek: string; vk: string }>;
  tags: Record<string, string[]>;
}

export default function BackupView({ onBack }: { onBack: () => void }) {
  const [entries, setEntries] = useState<BackupEntry[]>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string>('');
  const [success, setSuccess] = useState<string>('');
  const [selectedId, setSelectedId] = useState<string>('');
  const [includeLater, setIncludeLater] = useState<boolean>(true);
  const [merged, setMerged] = useState<{ items: RestoreItem[]; tags: RestoreTags[] } | null>(null);
  const [current, setCurrent] = useState<Current>({ variants: {}, tags: {} });
  const [sel, setSel] = useState<Record<string, boolean>>({});
  const [detailLoading, setDetailLoading] = useState<boolean>(false);
  const [confirm, setConfirm] = useState<boolean>(false);
  const [working, setWorking] = useState<boolean>(false);

  const reload = async (): Promise<BackupEntry[]> => {
    const list = await loadBackups();
    setEntries(list);
    return list;
  };

  useEffect(() => {
    (async () => {
      try {
        await reload();
      } catch (e: any) {
        setError(`Backups konnten nicht geladen werden: ${e?.message || 'Fehler'}`);
      }
      setLoading(false);
    })();
  }, []);

  // Aktuelle Werte der betroffenen Varianten/Produkte aus Shopify holen
  const fetchCurrent = async (m: { items: RestoreItem[]; tags: RestoreTags[] }): Promise<Current> => {
    const cur: Current = { variants: {}, tags: {} };
    const vIds = m.items.map((i) => gidVariant(i.v));
    for (let i = 0; i < vIds.length; i += 100) {
      const { data, errors } = await gql(
        `query Cur($ids: [ID!]!) { nodes(ids: $ids) { ... on ProductVariant { id price einkaufspreis: metafield(namespace: "custom", key: "einkaufspreis") { value } } } }`,
        { ids: vIds.slice(i, i + 100) },
      );
      if (errors.length) throw new Error(errors.map((e: any) => e.message).join(', '));
      for (const n of data?.nodes || []) {
        if (!n) continue;
        let ek = '';
        const raw = n.einkaufspreis?.value;
        if (raw) {
          try {
            ek = raw.trim().startsWith('{') ? String(JSON.parse(raw).amount ?? '') : raw;
          } catch {
            ek = '';
          }
        }
        cur.variants[String(n.id).split('/').pop()!] = { ek, vk: n.price || '' };
      }
    }
    const pIds = m.tags.map((t) => gidProduct(t.p));
    for (let i = 0; i < pIds.length; i += 100) {
      const { data, errors } = await gql(`query CurProducts($ids: [ID!]!) { nodes(ids: $ids) { ... on Product { id tags } } }`, {
        ids: pIds.slice(i, i + 100),
      });
      if (errors.length) throw new Error(errors.map((e: any) => e.message).join(', '));
      for (const n of data?.nodes || []) if (n) cur.tags[String(n.id).split('/').pop()!] = n.tags || [];
    }
    return cur;
  };

  const itemDiffers = (it: RestoreItem, cur: Current): boolean => {
    const c = cur.variants[it.v];
    if (!c) return false;
    return (it.ek !== undefined && it.ek !== '' && !same(it.ek, c.ek)) || (it.vk !== undefined && it.vk !== '' && !same(it.vk, c.vk));
  };
  const tagEffective = (t: RestoreTags, cur: Current): { add: string[]; remove: string[] } => {
    const now = cur.tags[t.p] || [];
    return { add: t.add.filter((x) => !has(now, x)), remove: t.remove.filter((x) => has(now, x)) };
  };

  const open = async (id: string, later: boolean, list: BackupEntry[] = entries): Promise<void> => {
    setSelectedId(id);
    setConfirm(false);
    setError('');
    setSuccess('');
    setDetailLoading(true);
    try {
      const m = mergeForRestore(list, id, later);
      const cur = await fetchCurrent(m);
      const s: Record<string, boolean> = {};
      for (const it of m.items) s[`i:${it.v}`] = itemDiffers(it, cur);
      for (const t of m.tags) {
        const e = tagEffective(t, cur);
        s[`t:${t.p}`] = e.add.length + e.remove.length > 0;
      }
      setMerged(m);
      setCurrent(cur);
      setSel(s);
    } catch (e: any) {
      setError(`Aktuelle Werte konnten nicht geladen werden: ${e?.message || 'Fehler'}`);
      setMerged(null);
    }
    setDetailLoading(false);
  };

  const download = (): void => {
    try {
      const blob = new Blob([JSON.stringify(entries, null, 2)], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `preis-backups-${new Date().toISOString().slice(0, 10)}.json`;
      a.click();
      setTimeout(() => URL.revokeObjectURL(url), 5000);
    } catch (e: any) {
      setError(`Download fehlgeschlagen: ${e?.message || 'Fehler'}`);
    }
  };

  const restore = async (): Promise<void> => {
    if (!merged) return;
    setWorking(true);
    setError('');
    setSuccess('');
    const itemsSel = merged.items.filter((it) => sel[`i:${it.v}`] && itemDiffers(it, current));
    const tagsSel = merged.tags.filter((t) => sel[`t:${t.p}`]).map((t) => ({ t, e: tagEffective(t, current) })).filter((x) => x.e.add.length + x.e.remove.length > 0);

    // 1) Sicherung des aktuellen Stands (damit die Wiederherstellung selbst rückgängig gemacht werden kann)
    try {
      await saveBackupEntry({
        id: new Date().toISOString(),
        src: 'restore',
        n: `Wiederherstellung auf den Stand vor ${fmtDate(selectedId)}`,
        items: itemsSel.map((it) => {
          const c = current.variants[it.v];
          const doEk = it.ek !== undefined && it.ek !== '' && !same(it.ek, c.ek);
          const doVk = it.vk !== undefined && it.vk !== '' && !same(it.vk, c.vk);
          return {
            v: it.v,
            p: it.p,
            s: it.s,
            t: it.t,
            c: it.c,
            ...(doEk ? { ek: [c.ek, it.ek!] as [string, string] } : {}),
            ...(doVk ? { vk: [c.vk, it.vk!] as [string, string] } : {}),
          };
        }),
        tags: tagsSel.map(({ t, e }) => ({ p: t.p, s: t.s, a: e.add, r: e.remove })),
      });
    } catch (e: any) {
      setError(`Sicherung des aktuellen Stands fehlgeschlagen – es wurde nichts geändert: ${e?.message || 'Fehler'}`);
      setWorking(false);
      setConfirm(false);
      return;
    }

    const problems: string[] = [];
    let ok = 0;
    const errText = (errors: any[], ue?: any[]): string =>
      [...(errors || []).map((x: any) => x.message), ...(ue || []).map((x: any) => x.message)].join(', ');

    // 2) Einkaufspreise (Metafeld, Typ money) in Paketen zu je 25
    const ekItems = itemsSel.filter((it) => it.ek !== undefined && it.ek !== '' && !same(it.ek, current.variants[it.v].ek));
    for (let i = 0; i < ekItems.length; i += 25) {
      const chunk = ekItems.slice(i, i + 25);
      const res = await gql(
        `mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) { metafieldsSet(metafields: $metafields) { metafields { id } userErrors { field message } } }`,
        {
          metafields: chunk.map((it) => ({
            ownerId: gidVariant(it.v),
            namespace: 'custom',
            key: 'einkaufspreis',
            type: 'money',
            value: JSON.stringify({ amount: Number(it.ek).toFixed(2), currency_code: it.c || 'EUR' }),
          })),
        },
      );
      const m = errText(res.errors, res.data?.metafieldsSet?.userErrors);
      if (m) problems.push(`EK ${chunk.map((x) => x.s).join(', ')}: ${m}`);
      else ok += chunk.length;
    }

    // 3) Verkaufspreise je Produkt
    const vkByProduct = new Map<string, RestoreItem[]>();
    for (const it of itemsSel.filter((x) => x.vk !== undefined && x.vk !== '' && !same(x.vk, current.variants[x.v].vk))) {
      vkByProduct.set(it.p, [...(vkByProduct.get(it.p) || []), it]);
    }
    for (const [p, list] of vkByProduct) {
      const res = await gql(
        `mutation VariantPrices($productId: ID!, $variants: [ProductVariantsBulkInput!]!) { productVariantsBulkUpdate(productId: $productId, variants: $variants) { productVariants { id price } userErrors { field message } } }`,
        { productId: gidProduct(p), variants: list.map((it) => ({ id: gidVariant(it.v), price: Number(it.vk).toFixed(2) })) },
      );
      const m = errText(res.errors, res.data?.productVariantsBulkUpdate?.userErrors);
      if (m) problems.push(`VK ${list.map((x) => x.s).join(', ')}: ${m}`);
      else ok += list.length;
    }

    // 4) Tags
    for (const { t, e } of tagsSel) {
      let msg = '';
      if (e.add.length) {
        const r = await gql(`mutation TagsAdd($id: ID!, $tags: [String!]!) { tagsAdd(id: $id, tags: $tags) { userErrors { message } } }`, {
          id: gidProduct(t.p),
          tags: e.add,
        });
        msg += errText(r.errors, r.data?.tagsAdd?.userErrors);
      }
      if (e.remove.length && !msg) {
        const r = await gql(`mutation TagsRemove($id: ID!, $tags: [String!]!) { tagsRemove(id: $id, tags: $tags) { userErrors { message } } }`, {
          id: gidProduct(t.p),
          tags: e.remove,
        });
        msg += errText(r.errors, r.data?.tagsRemove?.userErrors);
      }
      if (msg) problems.push(`Tags ${t.s}: ${msg}`);
      else ok++;
    }

    setConfirm(false);
    setWorking(false);
    if (problems.length) setError(`Fehler bei: ${problems.join(' | ')}`);
    setSuccess(`${ok} Änderungen wiederhergestellt. Der Stand davor wurde als „Wiederherstellung" gesichert.`);
    try {
      const list = await reload();
      await open(selectedId, includeLater, list);
      setSuccess(`${ok} Änderungen wiederhergestellt. Der Stand davor wurde als „Wiederherstellung" gesichert.`);
    } catch {
      /* Anzeige bleibt wie sie ist */
    }
  };

  if (loading) {
    return (
      <s-page heading="Preis-Backups">
        <s-section>
          <s-text>Backups werden geladen …</s-text>
          <s-spinner />
        </s-section>
      </s-page>
    );
  }

  const selectedCount = merged
    ? merged.items.filter((it) => sel[`i:${it.v}`] && itemDiffers(it, current)).length +
      merged.tags.filter((t) => sel[`t:${t.p}`] && tagEffective(t, current).add.length + tagEffective(t, current).remove.length > 0).length
    : 0;

  return (
    <s-page heading="Preis-Backups">
      <s-section>
        {error && <s-banner tone="critical">{error}</s-banner>}
        {success && <s-banner tone="success">{success}</s-banner>}
        <s-text>
          Vor jeder Speicherung in „Preisanpassung manuell" und „Einkaufspreise mit Keramix-Preisliste aktualisieren" werden die
          alten Werte der betroffenen Produkte als Datei auf dem Server gesichert. Backups werden nach {getRetentionDays()} Tagen
          automatisch gelöscht. Wählen Sie einen Zeitpunkt, um den Stand davor wiederherzustellen.
        </s-text>
        <s-stack direction="inline" gap="base">
          <s-button onClick={onBack}>Zurück</s-button>
          <s-button onClick={download} disabled={(entries.length === 0) || undefined}>
            Alle Backups als Datei herunterladen
          </s-button>
        </s-stack>
      </s-section>

      <s-section heading={`Gespeicherte Änderungen (${entries.length})`}>
        {entries.length === 0 ? (
          <s-text>Noch keine Backups vorhanden. Sie entstehen automatisch beim nächsten Speichern.</s-text>
        ) : (
          <s-table>
            <s-table-header-row>
              <s-table-header listSlot="primary">Zeitpunkt</s-table-header>
              <s-table-header listSlot="labeled">Quelle</s-table-header>
              <s-table-header listSlot="labeled">Preise</s-table-header>
              <s-table-header listSlot="labeled">Tags</s-table-header>
              <s-table-header listSlot="labeled">Hinweis</s-table-header>
              <s-table-header listSlot="labeled">Aktion</s-table-header>
            </s-table-header-row>
            <s-table-body>
              {entries.map((e) => (
                <s-table-row key={e.id}>
                  <s-table-cell>{fmtDate(e.id)}</s-table-cell>
                  <s-table-cell>{SRC_LABEL[e.src] || e.src}</s-table-cell>
                  <s-table-cell>{e.items.length}</s-table-cell>
                  <s-table-cell>{e.tags.length}</s-table-cell>
                  <s-table-cell>{e.n || '–'}</s-table-cell>
                  <s-table-cell>
                    <s-button onClick={() => void open(e.id, includeLater)} disabled={(detailLoading || working) || undefined}>
                      {selectedId === e.id ? 'Ausgewählt' : 'Stand davor ansehen'}
                    </s-button>
                  </s-table-cell>
                </s-table-row>
              ))}
            </s-table-body>
          </s-table>
        )}
      </s-section>

      {selectedId && (
        <s-section heading={`Stand vor ${fmtDate(selectedId)} wiederherstellen`}>
          <s-stack direction="block" gap="base">
            <s-checkbox
              label="Auch alle späteren Änderungen zurücknehmen (Stand genau zu diesem Zeitpunkt)"
              checked={includeLater || undefined}
              ref={listen('change', (e: any) => {
                const v = !!e.currentTarget.checked;
                setIncludeLater(v);
                void open(selectedId, v);
              })}
            />
            {detailLoading && <s-spinner />}
            {merged && !detailLoading && (
              <>
                <s-text>
                  Zielwerte = Stand vor der Änderung. „Aktuell" sind die jetzt im Shop gespeicherten Werte. Nur abweichende Zeilen sind
                  vorausgewählt.
                </s-text>

                {merged.items.length > 0 && (
                  <s-table>
                    <s-table-header-row>
                      <s-table-header listSlot="primary">SKU</s-table-header>
                      <s-table-header listSlot="labeled">Bezeichnung</s-table-header>
                      <s-table-header listSlot="labeled">EK Ziel</s-table-header>
                      <s-table-header listSlot="labeled">EK aktuell</s-table-header>
                      <s-table-header listSlot="labeled">VK Ziel</s-table-header>
                      <s-table-header listSlot="labeled">VK aktuell</s-table-header>
                      <s-table-header listSlot="labeled">Wiederherstellen</s-table-header>
                    </s-table-header-row>
                    <s-table-body>
                      {merged.items.slice(0, 1000).map((it) => {
                        const c = current.variants[it.v];
                        const diff = itemDiffers(it, current);
                        return (
                          <s-table-row key={it.v}>
                            <s-table-cell>{it.s}</s-table-cell>
                            <s-table-cell>{it.t}</s-table-cell>
                            <s-table-cell>{it.ek ?? '–'}</s-table-cell>
                            <s-table-cell>{c ? c.ek || '–' : 'nicht gefunden'}</s-table-cell>
                            <s-table-cell>{it.vk ?? '–'}</s-table-cell>
                            <s-table-cell>{c ? c.vk || '–' : 'nicht gefunden'}</s-table-cell>
                            <s-table-cell>
                              {diff ? (
                                <s-checkbox
                                  accessibilityLabel={`${it.s} wiederherstellen`}
                                  checked={sel[`i:${it.v}`] || undefined}
                                  ref={listen('change', (e: any) => {
                                    const on = !!e.currentTarget.checked;
                                    setSel((prev) => ({ ...prev, [`i:${it.v}`]: on }));
                                  })}
                                />
                              ) : (
                                'bereits gleich ✓'
                              )}
                            </s-table-cell>
                          </s-table-row>
                        );
                      })}
                    </s-table-body>
                  </s-table>
                )}
                {merged.items.length > 1000 && <s-text>Angezeigt werden die ersten 1000 von {merged.items.length} Zeilen.</s-text>}

                {merged.tags.length > 0 && (
                  <s-table>
                    <s-table-header-row>
                      <s-table-header listSlot="primary">SKU(s)</s-table-header>
                      <s-table-header listSlot="labeled">Tags wieder hinzufügen</s-table-header>
                      <s-table-header listSlot="labeled">Tags wieder entfernen</s-table-header>
                      <s-table-header listSlot="labeled">Wiederherstellen</s-table-header>
                    </s-table-header-row>
                    <s-table-body>
                      {merged.tags.map((t) => {
                        const e = tagEffective(t, current);
                        const diff = e.add.length + e.remove.length > 0;
                        return (
                          <s-table-row key={t.p}>
                            <s-table-cell>{t.s}</s-table-cell>
                            <s-table-cell>{e.add.join(', ') || '–'}</s-table-cell>
                            <s-table-cell>{e.remove.join(', ') || '–'}</s-table-cell>
                            <s-table-cell>
                              {diff ? (
                                <s-checkbox
                                  accessibilityLabel={`${t.s} Tags wiederherstellen`}
                                  checked={sel[`t:${t.p}`] || undefined}
                                  ref={listen('change', (ev: any) => {
                                    const on = !!ev.currentTarget.checked;
                                    setSel((prev) => ({ ...prev, [`t:${t.p}`]: on }));
                                  })}
                                />
                              ) : (
                                'bereits gleich ✓'
                              )}
                            </s-table-cell>
                          </s-table-row>
                        );
                      })}
                    </s-table-body>
                  </s-table>
                )}

                {merged.items.length === 0 && merged.tags.length === 0 && <s-text>Dieser Eintrag enthält keine Änderungen.</s-text>}

                {!confirm ? (
                  <s-button
                    variant="primary"
                    onClick={() => setConfirm(true)}
                    disabled={(working || selectedCount === 0) || undefined}
                  >
                    {`Ausgewählte wiederherstellen (${selectedCount})`}
                  </s-button>
                ) : (
                  <s-banner tone="warning">
                    <s-stack direction="block" gap="base">
                      <s-text>
                        {selectedCount} Einträge werden auf den Stand vor {fmtDate(selectedId)} zurückgesetzt. Der aktuelle Stand wird vorher
                        als Backup gesichert. Fortfahren?
                      </s-text>
                      <s-stack direction="inline" gap="base">
                        <s-button onClick={() => setConfirm(false)} disabled={working || undefined}>
                          Abbrechen
                        </s-button>
                        <s-button variant="primary" onClick={() => void restore()} disabled={working || undefined}>
                          {working ? 'Stelle wieder her...' : 'Ja, jetzt wiederherstellen'}
                        </s-button>
                      </s-stack>
                      {working && <s-spinner />}
                    </s-stack>
                  </s-banner>
                )}
              </>
            )}
          </s-stack>
        </s-section>
      )}
    </s-page>
  );
}
