/**
 * Zugriff der Oberfläche auf das eigene Backend (/api/*, gleiche Herkunft).
 * App Bridge hängt das Shopify-Sitzungstoken automatisch an fetch-Aufrufe an dieselbe Herkunft an;
 * es gibt keine BACKEND_URL und keinen Authorization-Header mehr.
 */
import { shopifyQuery } from './shopify-client';

export type OrderStatus = 'open' | 'processing' | 'completed';

export interface ApiOrderItem {
  sku: string;
  quantity: number;
  title: string;
  lagerstandort?: string;
}

export interface ApiOrder {
  id: string;
  date: string; // YYYY-MM-DD
  status: OrderStatus;
  items: ApiOrderItem[];
  trackingNumber?: string;
  verbuchtAm?: string;
}

export interface ApiHistoryEntry {
  datum: string;
  uhrzeit: string;
  ereignis: string;
  bestelldatum: string;
  sku: string;
  bezeichnung: string;
  mengeBestellt: number;
  mengeVerbucht: number;
  details: string;
}

export interface ReceivingResult {
  sku: string;
  title: string;
  quantity: number;
  receivedQuantity: number;
  outcome: 'booked' | 'skipped';
  error?: string;
}

export interface ReceivingProgress {
  current: number; // bereits abgeschlossene Artikel
  total: number;
  sku: string; // Artikel, der gerade bearbeitet wird
  phase: string;
}

export class BackendError extends Error {
  status: number;
  constructor(message: string, status: number) {
    super(message);
    this.status = status;
  }
}


const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));

// 410 = Shopify verlangt ein neues Sitzungstoken (App Bridge wiederholt selbst; hier zusätzlich abgesichert).
const isRetryable = (status: number): boolean => status >= 500 || status === 429 || status === 410;

async function request<T>(
  method: 'GET' | 'POST' | 'PUT' | 'DELETE',
  path: string,
  body?: unknown,
  attempts = 3,
): Promise<T> {
  let lastError: BackendError = new BackendError('Unbekannter Fehler', 0);

  for (let attempt = 1; attempt <= attempts; attempt++) {
    try {
      const response = await fetch(path, {
        method,
        headers: { 'Content-Type': 'application/json' },
        body: body === undefined ? undefined : JSON.stringify(body),
      });

      let payload: any = null;
      try {
        payload = await response.json();
      } catch {
        payload = null;
      }

      if (response.ok) return payload as T;

      lastError = new BackendError(payload?.error || `Backend-Fehler ${response.status}`, response.status);
      // Nur Serverfehler und Überlastung wiederholen, Fehler der Anfrage (4xx) nicht.
      if (!isRetryable(response.status)) throw lastError;
    } catch (err: any) {
      if (err instanceof BackendError && err.status > 0 && !isRetryable(err.status)) throw err;
      lastError =
        err instanceof BackendError
          ? err
          : new BackendError(`Backend nicht erreichbar (${err?.message || 'Netzwerkfehler'})`, 0);
    }
    if (attempt < attempts) await sleep(500 * attempt);
  }
  throw lastError;
}

const listOrders = (status = 'open,processing'): Promise<ApiOrder[]> =>
  request('GET', `/api/orders?status=${encodeURIComponent(status)}`);

const getOrder = (id: string): Promise<ApiOrder> =>
  request('GET', `/api/orders/${encodeURIComponent(id)}`);

/** Legt die Bestellung an und schreibt die Historie („Bestellung erfasst") in derselben Transaktion. */
async function createOrder(order: {
  id: string;
  date: string;
  items: ApiOrderItem[];
  trackingNumber?: string;
}): Promise<ApiOrder> {
  try {
    return await request<ApiOrder>('POST', '/api/orders', order);
  } catch (err: any) {
    // 409 nach einem Wiederholungsversuch: Die erste Anfrage war bereits angekommen.
    if (err instanceof BackendError && err.status === 409) return getOrder(order.id);
    throw err;
  }
}

const updateOrder = (
  id: string,
  order: { items: ApiOrderItem[]; trackingNumber?: string },
): Promise<ApiOrder> => request('PUT', `/api/orders/${encodeURIComponent(id)}`, order);

/** Löscht die Bestellung und schreibt „Bestellung gelöscht" je Position in die Historie. */
const deleteOrder = (id: string): Promise<{ deleted: boolean }> =>
  request('DELETE', `/api/orders/${encodeURIComponent(id)}`);

// ---------- Wareneingang ----------
const newBookingId = (): string =>
  `wareneingang-${Date.now()}-${Math.random().toString(36).substring(2, 12)}`;

/** Setzt die Sperre (Status „processing") und schreibt den Startvermerk. Wiederholung mit gleicher bookingId ist harmlos. */
const startReceiving = (
  orderId: string,
  info: { bookingId: string; positions: number; acceptedQuantity: number },
): Promise<{
  duplicate: boolean;
  previous: { status: OrderStatus; verbuchtAm: string | null } | null;
  order: ApiOrder;
}> => request('POST', `/api/orders/${encodeURIComponent(orderId)}/receiving/start`, info);

/** Schreibt das Ergebnis in die Historie und setzt „completed" (alles gebucht) oder lässt „processing" stehen. */
const finishReceiving = (
  orderId: string,
  info: { bookingId: string; results: ReceivingResult[] },
): Promise<{ duplicate: boolean; ok: boolean; order: ApiOrder }> =>
  request('POST', `/api/orders/${encodeURIComponent(orderId)}/receiving/finish`, info);

export interface ReceivingProgressInfo {
  bookingId: string;
  sku: string;
  title: string;
  quantity: number;
  receivedQuantity: number;
  status: 'pending' | 'booked' | 'skipped';
  error?: string;
}

/** Meldet den Fortschritt eines Artikels sofort an den Server (vor bzw. nach dem Shopify-Aufruf). */
const recordReceivingProgress = (
  orderId: string,
  info: ReceivingProgressInfo,
): Promise<{ duplicate: boolean; status: string }> =>
  request('POST', `/api/orders/${encodeURIComponent(orderId)}/receiving/progress`, info);

/** Artikel einer unterbrochenen Buchung, bei denen der Shopify-Bestand ungewiss ist. */
const getUnclearReceiving = (
  orderId: string,
): Promise<Array<{ sku: string; quantity: number; receivedQuantity: number; at: string }>> =>
  request('GET', `/api/orders/${encodeURIComponent(orderId)}/receiving/unclear`);

const getBookedQuantities = (orderId: string): Promise<{ [sku: string]: number }> =>
  request('GET', `/api/orders/${encodeURIComponent(orderId)}/booked`);

// ---------- Historie ----------
const getHistory = (year: string | number): Promise<ApiHistoryEntry[]> =>
  request('GET', `/api/history?year=${encodeURIComponent(String(year))}`);

/** Einzelne Ereignisse wie „Wareneingang abgebrochen"; Datum und Uhrzeit setzt der Server (Europe/Paris). */
const addHistoryEvents = (
  entries: Array<Partial<ApiHistoryEntry> & { ereignis: string; orderId?: string }>,
): Promise<{ added: number }> => request('POST', '/api/history', { entries });

// ---------- Migration aus den bisherigen Metafeldern ----------
export interface MetafieldSources {
  orders: unknown[];
  historySources: Array<{ name: string; entries: unknown[] }>;
  raw: { [key: string]: string };
}

/** Liest Bestellungen und alle Historien-Metafelder (auch den alten Schlüssel „bestellhistorie"). Verändert nichts. */
async function readMetafieldSources(): Promise<MetafieldSources> {
  const { data, errors } = await shopifyQuery(
    `query { shop { metafields(first: 50, namespace: "custom") { nodes { key value } } } }`,
  );
  if (errors?.length > 0) {
    throw new Error(errors.map((e: any) => e.message).join(', '));
  }

  const raw: { [key: string]: string } = {};
  for (const node of data?.shop?.metafields?.nodes ?? []) {
    if (node.key === 'purchase_orders' || String(node.key).startsWith('bestellhistorie')) {
      raw[node.key] = node.value;
    }
  }

  const parse = (key: string): unknown[] => {
    let value: unknown;
    try {
      value = JSON.parse(raw[key]);
    } catch {
      throw new Error(`Metafeld "${key}" enthält kein gültiges JSON – Migration abgebrochen.`);
    }
    if (!Array.isArray(value)) throw new Error(`Metafeld "${key}" ist kein Array – Migration abgebrochen.`);
    return value;
  };

  const orders = raw.purchase_orders ? parse('purchase_orders') : [];
  // Älteste Quelle zuerst: erst der alte Schlüssel ohne Jahr, dann die Jahres-Metafelder aufsteigend.
  const historyKeys = Object.keys(raw)
    .filter((key) => key.startsWith('bestellhistorie'))
    .sort();
  const historySources = historyKeys.map((key) => ({ name: key, entries: parse(key) }));

  return { orders, historySources, raw };
}

export interface ImportReport {
  dryRun: boolean;
  orders: { received: number; toInsert: number; skippedExisting: number; invalid: number };
  history: { received: number; toInsert: number; duplicatesSkipped: number; invalid: number };
  invalidExamples: string[];
  totals: { orders: number; history: number };
}

/** dryRun=true: nur zählen und prüfen, es wird nichts geschrieben. Der Import kann gefahrlos wiederholt werden. */
async function migrateFromMetafields(
  sources: MetafieldSources,
  dryRun: boolean,
): Promise<ImportReport> {
  return request<ImportReport>(
    'POST',
    `/api/import${dryRun ? '?dryRun=1' : ''}`,
    { orders: sources.orders, historySources: sources.historySources },
    1,
  );
}

/** Sicherungskopie der Roh-Metafelder als Blob-URL (Rechtsklick → Link speichern, wie bei der Druckansicht). */
function makeRawBackupUrl(sources: MetafieldSources): string {
  const blob = new Blob([JSON.stringify({ savedAt: new Date().toISOString(), metafields: sources.raw }, null, 2)], {
    type: 'application/json',
  });
  return URL.createObjectURL(blob);
}

const getStats = (): Promise<any> => request('GET', '/api/stats');

/** Einstellungen (aktuell nur "location_id"). */
const getSetting = (key: string): Promise<{ key: string; value: string }> =>
  request('GET', `/api/settings/${encodeURIComponent(key)}`);
const setSetting = (key: string, value: string): Promise<{ key: string; value: string }> =>
  request('PUT', `/api/settings/${encodeURIComponent(key)}`, { value });

/** Diagnose: aktive Datenbank, Zähler, Node-/SQLite-Versionen. */
const getDiag = (): Promise<any> => request('GET', '/api/diag');

/** Legt eine Druckansicht auf dem Server ab und liefert den Pfad (/print/<id>) für ein neues Fenster. */
const createPrint = (html: string): Promise<{ id: string; path: string }> =>
  request('POST', '/api/print', { html });

// Alle Backend-Aufrufe laufen über dieses Objekt (Namen wie deleteOrder/startReceiving gibt es auch in der Komponente).
export const backend = {
  listOrders,
  getOrder,
  createOrder,
  updateOrder,
  deleteOrder,
  newBookingId,
  startReceiving,
  finishReceiving,
  getBookedQuantities,
  recordReceivingProgress,
  getUnclearReceiving,
  getHistory,
  addHistoryEvents,
  getStats,
  getSetting,
  setSetting,
  getDiag,
  createPrint,
  readMetafieldSources,
  migrateFromMetafields,
  makeRawBackupUrl,
};


