/**
 * Zugriff der Admin-Erweiterung auf das SQLite-Backend (Novatrend).
 * Datei nach src/backend.ts der Erweiterung legen und dort importieren.
 *
 * - Das Shopify-ID-Token gilt nur eine Minute und wird deshalb bei JEDEM Aufruf frisch geholt.
 * - Die Antwortformen entsprechen den bisherigen Typen der App (PurchaseOrder, HistoryEntry).
 */
declare const shopify: any;

// Adresse des Backends. Läuft die App unter einem Pfad (z. B. /bestellverwaltung), diesen anhängen.
export const BACKEND_URL = 'https://bestellverwaltung.ceramique-peinture.fr';

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;
  }
}

async function getToken(): Promise<string> {
  const token: string | null | undefined =
    (await shopify.auth?.idToken?.()) ?? (await shopify.idToken?.());
  if (!token) throw new BackendError('Kein Shopify-ID-Token verfügbar', 0);
  return token;
}

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

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 token = await getToken();
      const response = await fetch(`${BACKEND_URL}${path}`, {
        method,
        headers: {
          Authorization: `Bearer ${token}`,
          '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 (response.status < 500 && response.status !== 429) throw lastError;
    } catch (err: any) {
      if (err instanceof BackendError && err.status > 0 && err.status < 500 && err.status !== 429) 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;
}

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

export 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. */
export 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;
  }
}

export 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. */
export const deleteOrder = (id: string): Promise<{ deleted: boolean }> =>
  request('DELETE', `/api/orders/${encodeURIComponent(id)}`);

// ---------- Wareneingang ----------
export 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. */
export 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. */
export 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 const getBookedQuantities = (orderId: string): Promise<{ [sku: string]: number }> =>
  request('GET', `/api/orders/${encodeURIComponent(orderId)}/booked`);

// ---------- Historie ----------
export 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). */
export 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. */
export async function readMetafieldSources(): Promise<MetafieldSources> {
  const { data, errors } = await shopify.query(
    `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. */
export 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). */
export 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);
}
