import { ID, Query, databaseId, tables } from './appwrite';
import { BUCKET_BILDER, BUCKET_REFERENZEN as BUCKET } from './dateien';
import { teamRechte } from './permissions';
import { hochladen, type Auswahl } from './upload';

/**
 * „Modell“ heißt in diesem Projekt **immer** das Asset – Person, Produkt,
 * Kulisse oder Logo. Das generierende KI-Modell heißt ausgeschrieben
 * (projekt-uebersicht.md, Sprachregelung).
 */
export type AssetTyp = 'gesicht' | 'produkt' | 'kulisse' | 'logo' | 'sonstiges';

export type Asset = {
  $id: string;
  brand_id: string;
  typ: AssetTyp;
  name: string;
  released_version_id?: string;
  ist_teilbar?: boolean;
};

export type AssetVersion = {
  $id: string;
  asset_id: string;
  version_no: number;
  status: 'entwurf' | 'freigegeben' | 'archiviert';
  beschreibung_md?: string;
  merkmale?: string[];
  change_reason?: string;
  /** EINGABE – was der Nutzer hochgeladen hat. */
  reference_file_ids?: string[];
  /** ERGEBNIS – das für diese Version erzeugte Bild. */
  bild_file_id?: string;
};

export const TYPEN: { wert: AssetTyp; titel: string; erklaerung: string; icon: string }[] = [
  { wert: 'gesicht', titel: 'Person', erklaerung: 'Ein Gesicht, das in Bildern wiederkehrt.', icon: 'person-outline' },
  { wert: 'produkt', titel: 'Produkt', erklaerung: 'Ein Objekt, das genau so aussehen muss wie im Regal.', icon: 'cube-outline' },
  { wert: 'kulisse', titel: 'Kulisse', erklaerung: 'Ein Ort oder Hintergrund, vor dem gearbeitet wird.', icon: 'image-outline' },
  { wert: 'logo', titel: 'Logo', erklaerung: 'Markenzeichen, das nie verfremdet werden darf.', icon: 'ribbon-outline' },
];

export { BUCKET_REFERENZEN, dateiUrl } from './dateien';

export async function assetListe(brandId: string, typ?: AssetTyp): Promise<Asset[]> {
  const queries = [Query.equal('brand_id', brandId), Query.limit(100)];
  if (typ) queries.push(Query.equal('typ', typ));
  const res = await tables.listRows({ databaseId, tableId: 'assets', queries });
  return res.rows as unknown as Asset[];
}

export async function versionLesen(id: string): Promise<AssetVersion | null> {
  try {
    const row = await tables.getRow({ databaseId, tableId: 'asset_versions', rowId: id });
    return row as unknown as AssetVersion;
  } catch {
    return null;
  }
}

export async function versionenZuAsset(assetId: string): Promise<AssetVersion[]> {
  const res = await tables.listRows({
    databaseId,
    tableId: 'asset_versions',
    queries: [Query.equal('asset_id', assetId), Query.limit(50)],
  });
  return res.rows as unknown as AssetVersion[];
}

/**
 * Wieviele Varianten beim Anlegen erzeugt werden.
 *
 * Vier, weil §8 vier bis sechs Referenzbilder als Optimum nennt und weil man
 * aus vier Vorschlägen noch bewusst auswählt – bei acht klickt man das erste
 * Passable an, und das Auswahlsignal wird wertlos.
 */
export const VARIANTEN_JE_MODELL = 4;

/**
 * Typen, für die beim Anlegen Varianten erzeugt werden.
 *
 * **Logo ist ein Sonderfall.** Die Brand-Regel verbietet, das Markenzeichen zu
 * verzerren, zu beschneiden, umzufärben oder neu zu interpretieren. Sie schützt
 * damit ein **vorhandenes** Logo – eine Marke ohne Logo hat nichts zu schützen.
 *
 * Daraus folgen zwei Wege, die sich gegenseitig ausschließen:
 * - **Logo hochladen** → wird v1 und sofort freigegeben. Ab da wird es nie
 *   wieder generiert, nur noch unverändert eingesetzt. Das ist der Normalfall.
 * - **Logo entwerfen lassen** → nur wenn *keine* Datei hochgeladen wird. Es gibt
 *   dann kein Original, das verfremdet werden könnte; es entsteht ein Vorschlag.
 */
export const GENERIERBAR: AssetTyp[] = ['gesicht', 'produkt', 'kulisse'];

/** Kann für diesen Typ und diese Eingabe generiert werden? */
export function kannGenerieren(typ: AssetTyp, hatReferenzbilder: boolean): boolean {
  if (GENERIERBAR.includes(typ)) return true;
  // Logo: nur entwerfen, wenn nichts hochgeladen wurde.
  return typ === 'logo' && !hatReferenzbilder;
}

/**
 * Legt ein Modell an.
 *
 * **Logo:** nur hochladen. Eine der globalen Brand-Regeln verbietet es
 * ausdrücklich, das Logo generieren zu lassen – es darf nie verfremdet,
 * beschnitten oder neu interpretiert werden. Version 1 ist deshalb direkt die
 * freigegebene Version mit der hochgeladenen Datei.
 *
 * **Person, Produkt, Kulisse:** vier Entwurfs-Versionen plus je ein
 * `modell_gen`-Job. Freigegeben wird **nichts** – welche Variante gewinnt,
 * entscheidet der Nutzer im Ergebnis-Screen. Das ist das Release-Prinzip aus
 * §4: es wird immer eine bewusst freigegebene Version verwendet, nie die
 * neueste, damit das Aussehen nicht unbemerkt wegdriftet.
 */
export async function assetAnlegen(
  brandId: string,
  teamId: string,
  daten: { typ: AssetTyp; name: string; beschreibung: string; merkmale: string[]; bilder: Auswahl[] },
): Promise<Asset> {
  const rechte = teamRechte(teamId);

  const fileIds: string[] = [];
  for (const bild of daten.bilder) {
    fileIds.push(await hochladen(BUCKET, bild, rechte));
  }

  const asset = (await tables.createRow({
    databaseId,
    tableId: 'assets',
    rowId: ID.unique(),
    data: { brand_id: brandId, typ: daten.typ, name: daten.name, ist_teilbar: false },
    permissions: rechte,
  })) as unknown as Asset;

  const basis = {
    asset_id: asset.$id,
    beschreibung_md: daten.beschreibung,
    // Token-Locking: diese Zeichenfolgen setzt der Prompt **wörtlich** ein.
    // Umschreiben heißt hier, die Identität zu verlieren.
    merkmale: daten.merkmale,
    reference_file_ids: fileIds,
  };

  if (!kannGenerieren(daten.typ, fileIds.length > 0)) {
    const version = (await tables.createRow({
      databaseId,
      tableId: 'asset_versions',
      rowId: ID.unique(),
      data: { ...basis, version_no: 1, status: 'freigegeben' },
      permissions: rechte,
    })) as unknown as AssetVersion;

    await tables.updateRow({
      databaseId, tableId: 'assets', rowId: asset.$id,
      data: { released_version_id: version.$id },
    });
    return { ...asset, released_version_id: version.$id };
  }

  for (let i = 1; i <= VARIANTEN_JE_MODELL; i++) {
    const version = (await tables.createRow({
      databaseId,
      tableId: 'asset_versions',
      rowId: ID.unique(),
      data: { ...basis, version_no: i, status: 'entwurf', change_reason: `Variante ${i} der ersten Runde` },
      permissions: rechte,
    })) as unknown as AssetVersion;

    await tables.createRow({
      databaseId,
      tableId: 'jobs',
      rowId: ID.unique(),
      data: {
        brand_id: brandId,
        typ: 'modell_gen',
        prompt_template_key: 'P7',
        status: 'wartend',
        refs: JSON.stringify({ asset_id: asset.$id, version_id: version.$id, variante: i }),
      },
      permissions: rechte,
    });
  }

  return asset;
}

/**
 * Startet eine neue Runde Varianten auf einem bestehenden Modell.
 *
 * Die freigegebene Version bleibt freigegeben, bis der Nutzer bewusst eine neue
 * wählt – das Release-Prinzip aus §4 gilt auch hier. Bis dahin arbeitet die App
 * unverändert weiter mit dem alten Stand, statt heimlich auf einen Entwurf
 * umzuschwenken.
 *
 * `grund` beschreibt, was anders werden soll. Er landet in `change_reason` und
 * geht zusätzlich in den Prompt – ohne ihn wäre eine zweite Runde nur ein
 * neuer Würfelwurf mit demselben Text.
 */
export async function neueRunde(
  brandId: string,
  teamId: string,
  assetId: string,
  daten: { beschreibung: string; merkmale: string[]; grund: string },
): Promise<void> {
  const rechte = teamRechte(teamId);
  const vorhanden = await versionenZuAsset(assetId);
  const start = Math.max(0, ...vorhanden.map((v) => v.version_no));
  // Referenzen der ersten Version mitnehmen – sie sind die Vorlage des Modells.
  const referenzen = vorhanden.sort((a, b) => a.version_no - b.version_no)[0]?.reference_file_ids ?? [];

  for (let i = 1; i <= VARIANTEN_JE_MODELL; i++) {
    const version = (await tables.createRow({
      databaseId,
      tableId: 'asset_versions',
      rowId: ID.unique(),
      data: {
        asset_id: assetId,
        version_no: start + i,
        status: 'entwurf',
        beschreibung_md: daten.beschreibung,
        merkmale: daten.merkmale,
        reference_file_ids: referenzen,
        change_reason: daten.grund,
      },
      permissions: rechte,
    })) as unknown as AssetVersion;

    await tables.createRow({
      databaseId,
      tableId: 'jobs',
      rowId: ID.unique(),
      data: {
        brand_id: brandId,
        typ: 'modell_gen',
        prompt_template_key: 'P7',
        status: 'wartend',
        refs: JSON.stringify({ asset_id: assetId, version_id: version.$id, variante: i }),
      },
      permissions: rechte,
    });
  }
}

export type VariantenLage = { status: string; error?: string; variante: number };

/**
 * Der Job-Zustand je Version.
 *
 * Nötig, weil man einer Version selbst nicht ansieht, ob ihr Bild noch aussteht
 * oder die Generierung gescheitert ist – beides sieht nach „Entwurf ohne Bild"
 * aus. Der Grund steht ausschließlich im Job.
 */
export async function jobsZuAsset(assetId: string): Promise<Map<string, VariantenLage>> {
  const res = await tables.listRows({
    databaseId,
    tableId: 'jobs',
    queries: [Query.equal('typ', 'modell_gen'), Query.orderDesc('$createdAt'), Query.limit(100)],
  });
  const nach = new Map<string, VariantenLage>();
  for (const j of res.rows as unknown as { status: string; error?: string; refs?: string }[]) {
    try {
      const r = JSON.parse(j.refs ?? '{}');
      if (r.asset_id !== assetId || !r.version_id) continue;
      if (!nach.has(r.version_id)) nach.set(r.version_id, { status: j.status, error: j.error, variante: r.variante ?? 1 });
    } catch { /* kaputte refs überspringen */ }
  }
  return nach;
}

/** Stellt eine gescheiterte Variante erneut in die Warteschlange. */
export async function varianteWiederholen(
  brandId: string,
  teamId: string,
  assetId: string,
  versionId: string,
  variante: number,
): Promise<void> {
  await tables.createRow({
    databaseId,
    tableId: 'jobs',
    rowId: ID.unique(),
    data: {
      brand_id: brandId,
      typ: 'modell_gen',
      prompt_template_key: 'P7',
      status: 'wartend',
      refs: JSON.stringify({ asset_id: assetId, version_id: versionId, variante }),
    },
    permissions: teamRechte(teamId),
  });
}

/**
 * Gibt eine Version frei und archiviert die konkurrierenden.
 *
 * Archivieren statt löschen – ein verworfener Entwurf ist auch Wissen (§5), und
 * `won_against_version_id` hält fest, wogegen sich die Siegerin durchgesetzt hat.
 */
export async function versionFreigeben(assetId: string, versionId: string): Promise<void> {
  const alle = await versionenZuAsset(assetId);
  // Auch die **abgelöste** Version wird archiviert, nicht nur die Entwürfe.
  // Bliebe sie auf `freigegeben` stehen, gäbe es zwei Zeilen mit diesem Status,
  // von denen nur eine wirklich gilt – und die Anzeige zeigte sie als Entwurf.
  const verlierer = alle.filter((v) => v.$id !== versionId && v.status !== 'archiviert');

  await tables.updateRow({
    databaseId, tableId: 'asset_versions', rowId: versionId,
    data: {
      status: 'freigegeben',
      won_against_version_id: verlierer[0]?.$id ?? null,
    },
  });
  for (const v of verlierer) {
    await tables.updateRow({
      databaseId, tableId: 'asset_versions', rowId: v.$id, data: { status: 'archiviert' },
    });
  }
  await tables.updateRow({
    databaseId, tableId: 'assets', rowId: assetId, data: { released_version_id: versionId },
  });
}

export async function assetLoeschen(id: string): Promise<void> {
  await tables.deleteRow({ databaseId, tableId: 'assets', rowId: id });
}

export type AssetMitBild = Asset & {
  titelbildId?: string;
  /** In welchem Bucket das Titelbild liegt – generiert oder hochgeladen. */
  titelbildBucket?: string;
  /** true, solange keine Version freigegeben ist (Generierung läuft noch). */
  wartet?: boolean;
};

/**
 * Modelle samt Titelbild – das erste Referenzbild der **freigegebenen** Version.
 *
 * Holt alle Versionen in einer einzigen Abfrage statt einer je Modell:
 * `Query.equal` nimmt auch eine Liste. Bei 30 Kulissen wären das sonst 30
 * Rundreisen, nur um Vorschaubilder zu zeigen.
 */
export async function assetsMitBild(brandId: string, typ?: AssetTyp): Promise<AssetMitBild[]> {
  const liste = await assetListe(brandId, typ);
  if (!liste.length) return liste;

  // Alle Versionen aller Modelle in einer Abfrage – nicht eine je Modell.
  const res = await tables.listRows({
    databaseId,
    tableId: 'asset_versions',
    queries: [Query.equal('asset_id', liste.map((a) => a.$id)), Query.limit(200)],
  });
  const versionen = res.rows as unknown as AssetVersion[];

  return liste.map((a) => {
    const meine = versionen.filter((v) => v.asset_id === a.$id);
    const frei = meine.find((v) => v.$id === a.released_version_id);
    // Ist noch nichts freigegeben, zeigt die Kachel den ersten fertigen Entwurf –
    // sonst bliebe sie leer, obwohl schon Varianten da sind.
    const ersatz = meine.find((v) => v.bild_file_id) ?? meine[0];
    const quelle = frei ?? ersatz;

    return {
      ...a,
      titelbildId: quelle?.bild_file_id ?? quelle?.reference_file_ids?.[0],
      titelbildBucket: quelle?.bild_file_id ? BUCKET_BILDER : BUCKET,
      wartet: !a.released_version_id,
    };
  });
}

/**
 * Mehr als 7 Referenzbilder mitteln die Merkmale weg („feature averaging“),
 * 4–6 sind das Optimum (projekt-uebersicht.md §8).
 */
export const BILDER_MAX = 6;
export const BILDER_EMPFOHLEN = 4;
