import { createHash, randomUUID } from "node:crypto";
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { basename, extname } from "node:path";
import { Readable } from "node:stream";

import type BetterSqlite3 from "better-sqlite3";
import sharp, { type Metadata } from "sharp";

import type { StaticStickerCatalogItem } from "@dada/static-sticker-catalog";

import {
  auditRetentionMilliseconds,
  isSafeAuditRef,
  isSafeAuditSummaryJson,
  serializeAuditSummary,
} from "./audit-policy.js";
import { ManagedStorage, type StagedManagedFile } from "./managed-storage.js";
import { StickerReleaseError } from "./sticker-release-errors.js";
import { classifyCapacity } from "./storage-policy.js";

export { StickerReleaseError } from "./sticker-release-errors.js";

const require = createRequire(import.meta.url);
const Database = require("better-sqlite3") as typeof BetterSqlite3;
const stableIdPattern = /^STK([0-9]{4,})$/;
const idempotencyPattern = /^[A-Za-z0-9_-]{32,200}$/;
const sha256Pattern = /^[0-9a-f]{64}$/i;
const maximumOriginalBytes = 20 * 1024 * 1024;
const maximumDimension = 8_192;
const bundledPartCounts = [203, 36, 27, 48, 38, 75, 37, 67, 48, 24, 40, 30, 27, 51, 62, 19, 36, 45, 92, 53, 69, 31, 36, 30, 183] as const;

type StickerMime = "image/png" | "image/webp";
type StickerVariant = "original" | "thumbnail";

interface StickerItemRow {
  enabled: 0 | 1;
  height: number;
  mime_type: StickerMime;
  order_index: number;
  original_byte_size: number;
  original_file_id: string;
  original_filename: string;
  original_relative_path: string;
  original_sha256: string;
  part: number;
  release_version: string;
  stable_id: string;
  thumbnail_byte_size: number;
  thumbnail_file_id: string;
  thumbnail_relative_path: string;
  thumbnail_sha256: string;
  width: number;
}

export interface StickerUploadInput {
  actorId: string;
  content: Readable;
  enabled: boolean;
  expectedByteSize: number;
  expectedMimeType: StickerMime;
  expectedSha256: string;
  fileName: string;
  idempotencyKey: string;
  order: number;
  part: number;
  stableId: string;
}

function digest(value: string) {
  return createHash("sha256").update(value, "utf8").digest("hex");
}

function stableJson(value: unknown): string {
  if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
  if (value && typeof value === "object") {
    return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
  }
  return JSON.stringify(value);
}

function iso(timestamp: number) {
  return new Date(timestamp).toISOString();
}

function itemView(row: StickerItemRow): StaticStickerCatalogItem {
  const originalReference = `/api/v1/assets/public/${encodeURIComponent(row.release_version)}/${encodeURIComponent(row.stable_id)}`;
  return {
    enabled: row.enabled === 1,
    height: row.height,
    mime: row.mime_type,
    mime_type: row.mime_type,
    order: row.order_index,
    original_filename: row.original_filename,
    original_reference: originalReference,
    origin: "admin_uploaded",
    part: row.part,
    relative_path: `static-stickers/${row.stable_id}${row.mime_type === "image/png" ? ".png" : ".webp"}`,
    resource_version: row.release_version,
    sha256: row.original_sha256,
    stable_id: row.stable_id,
    thumbnail_reference: {
      media: "thumbnail",
      resource_id: row.stable_id,
      resource_version: row.release_version,
      url: `${originalReference}?variant=thumbnail`,
    },
    width: row.width,
  };
}

export class StickerReleaseService {
  private readonly clock: () => number;
  private readonly database: BetterSqlite3.Database;
  private readonly storage: ManagedStorage;

  constructor(input: { clock?: () => number; databasePath: string; storage: ManagedStorage }) {
    this.clock = input.clock ?? Date.now;
    const nativeBinding = process.env.DADA_SQLITE_NATIVE_BINDING;
    this.database = new Database(input.databasePath, nativeBinding ? { nativeBinding } : undefined);
    this.database.pragma("journal_mode = WAL");
    this.database.pragma("foreign_keys = ON");
    this.database.pragma("busy_timeout = 5000");
    this.database.function("dada_audit_ref_is_safe", { deterministic: true }, isSafeAuditRef);
    this.database.function("dada_audit_summary_is_safe", { deterministic: true }, isSafeAuditSummaryJson);
    this.database.function("dada_allow_privacy_purge", { deterministic: false }, () => 0);
    this.database.function("dada_privacy_purge_subject", { deterministic: false }, () => "");
    this.database.function("dada_allow_retention_purge", { deterministic: false }, () => 0);
    this.database.function("dada_retention_purge_now", { deterministic: false }, () => 0);
    this.storage = input.storage;
    this.migrate();
  }

  close() {
    this.database.close();
  }

  async upload(input: StickerUploadInput) {
    this.validateUpload(input);
    const requestHash = digest(stableJson({
      enabled: input.enabled,
      expected_byte_size: input.expectedByteSize,
      expected_mime_type: input.expectedMimeType,
      expected_sha256: input.expectedSha256.toLowerCase(),
      order: input.order,
      part: input.part,
      stable_id: input.stableId,
    }));
    const keyDigest = digest(input.idempotencyKey);
    const receipt = this.database.prepare(`
      SELECT request_hash, release_version FROM sticker_upload_receipts
      WHERE actor_id = ? AND idempotency_key_digest = ?
    `).get(input.actorId, keyDigest) as { release_version: string; request_hash: string } | undefined;
    if (receipt) {
      input.content.destroy();
      if (receipt.request_hash !== requestHash) throw new StickerReleaseError("sticker_idempotency_conflict", 409);
      return this.uploadResult(receipt.release_version, input.stableId, false);
    }
    this.assertNewPosition(input.stableId, input.part, input.order);

    const staged: StagedManagedFile[] = [];
    try {
      const original = await this.storage.stageManagedImage({
        content: input.content,
        expectedMimeType: input.expectedMimeType,
        expectedSha256: input.expectedSha256,
        fileKind: "sticker_original",
        fileName: `${input.stableId}${input.expectedMimeType === "image/png" ? ".png" : ".webp"}`,
        maximumBytes: maximumOriginalBytes,
        operationId: randomUUID(),
        ownerRef: input.actorId,
        projectedWriteBytes: input.expectedByteSize,
      });
      staged.push(original);
      if (original.bytes !== input.expectedByteSize) throw new StickerReleaseError("content_size_invalid");

      let metadata: Metadata;
      let thumbnail: Buffer;
      const decoder = sharp(readFileSync(original.stagingPath), { failOn: "warning", limitInputPixels: maximumDimension * maximumDimension });
      try {
        metadata = await decoder.metadata();
        if (metadata.format !== (input.expectedMimeType === "image/png" ? "png" : "webp")
          || !metadata.width || !metadata.height || metadata.width > maximumDimension || metadata.height > maximumDimension) {
          throw new Error("content_decode_invalid");
        }
        thumbnail = await decoder
          .rotate()
          .resize({ fit: "inside", height: 256, width: 256, withoutEnlargement: true })
          .png({ adaptiveFiltering: true, compressionLevel: 9 })
          .toBuffer();
      } catch {
        throw new StickerReleaseError("content_decode_invalid");
      } finally {
        decoder.destroy();
      }

      const thumbnailStaged = await this.storage.stageManagedImage({
        content: Readable.from(thumbnail),
        expectedMimeType: "image/png",
        fileKind: "sticker_thumbnail",
        fileName: `${input.stableId}-thumbnail.png`,
        maximumBytes: maximumOriginalBytes,
        operationId: randomUUID(),
        ownerRef: input.actorId,
        projectedWriteBytes: thumbnail.byteLength,
      });
      staged.push(thumbnailStaged);
      const releaseVersion = this.immediate(() => this.commitUpload({
        ...input,
        height: metadata.height!,
        keyDigest,
        original,
        requestHash,
        thumbnail: thumbnailStaged,
        width: metadata.width!,
      }));
      return this.uploadResult(releaseVersion, input.stableId, true);
    } catch (error) {
      for (const file of staged) this.storage.abandonStagedFile(file);
      if (!(error instanceof StickerReleaseError) && error instanceof Error
        && new Set(["content_hash_invalid", "content_mime_invalid", "content_size_invalid", "file_name_invalid"]).has(error.message)) {
        throw new StickerReleaseError("sticker_upload_invalid");
      }
      throw error;
    }
  }

  update(input: { actorId: string; enabled?: boolean; order?: number; part?: number; stableId: string }) {
    const current = this.currentVersion();
    if (!current) throw new StickerReleaseError("sticker_not_found", 404);
    const existing = this.readItem(current, input.stableId);
    if (!existing) throw new StickerReleaseError("sticker_not_found", 404);
    const part = input.part ?? existing.part;
    const order = input.order ?? existing.order_index;
    this.validatePosition(input.stableId, part, order);
    const releaseVersion = this.immediate(() => {
      const version = this.nextReleaseVersion();
      this.copyRelease(current, version);
      const conflict = this.database.prepare(`
        SELECT stable_id FROM sticker_release_items
        WHERE release_version = ? AND part = ? AND order_index = ? AND stable_id <> ?
      `).get(version, part, order, input.stableId);
      if (conflict) throw new StickerReleaseError("sticker_order_conflict", 409);
      this.database.prepare(`
        UPDATE sticker_release_items SET enabled = ?, part = ?, order_index = ?
        WHERE release_version = ? AND stable_id = ?
      `).run((input.enabled ?? existing.enabled === 1) ? 1 : 0, part, order, version, input.stableId);
      this.finalizeRelease(version, current, input.actorId);
      this.insertReleaseAudit({
        actorId: input.actorId,
        afterSummary: { enabled: input.enabled ?? existing.enabled === 1, order, part, stable_id: input.stableId },
        beforeSummary: { enabled: existing.enabled === 1, order: existing.order_index, part: existing.part, stable_id: input.stableId },
        operationType: "sticker_release_update",
        releaseVersion: version,
      });
      return version;
    });
    return { item: itemView(this.readItem(releaseVersion, input.stableId)!), release_version: releaseVersion };
  }

  listPublic(releaseVersion = this.currentVersion()) {
    if (!releaseVersion) return { count: 0, items: [], release_version: null };
    const exists = this.database.prepare("SELECT 1 FROM sticker_releases WHERE release_version = ?").get(releaseVersion);
    if (!exists) return { count: 0, items: [], release_version: null };
    const items = (this.database.prepare(`
      SELECT * FROM sticker_release_items WHERE release_version = ? AND enabled = 1
      ORDER BY part, order_index, stable_id
    `).all(releaseVersion) as StickerItemRow[]).map(itemView);
    return { count: items.length, items, release_version: releaseVersion };
  }

  adminView() {
    const releaseVersion = this.currentVersion();
    const items = releaseVersion
      ? (this.database.prepare("SELECT * FROM sticker_release_items WHERE release_version = ? ORDER BY part, order_index, stable_id").all(releaseVersion) as StickerItemRow[])
      : [];
    return {
      count: items.length,
      items: items.map((row) => ({
        ...itemView(row),
        file_state: "committed" as const,
        original_byte_size: row.original_byte_size,
        thumbnail_byte_size: row.thumbnail_byte_size,
      })),
      release_version: releaseVersion,
      storage: this.storage.getState(),
    };
  }

  readPublicAsset(releaseVersion: string, stableId: string, variant: StickerVariant) {
    const row = this.readItem(releaseVersion, stableId);
    if (!row || row.enabled !== 1) return undefined;
    const fileId = variant === "thumbnail" ? row.thumbnail_file_id : row.original_file_id;
    const path = this.storage.resolveManagedFile(fileId);
    if (!path) return undefined;
    return {
      bytes: readFileSync(path),
      mimeType: variant === "thumbnail" ? "image/png" as const : row.mime_type,
      sha256: variant === "thumbnail" ? row.thumbnail_sha256 : row.original_sha256,
    };
  }

  inspectCounts() {
    const count = (table: string) => (this.database.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get() as { count: number }).count;
    return { items: count("sticker_release_items"), releases: count("sticker_releases"), upload_receipts: count("sticker_upload_receipts") };
  }

  private uploadResult(releaseVersion: string, stableId: string, created: boolean) {
    const row = this.readItem(releaseVersion, stableId);
    if (!row) throw new StickerReleaseError("sticker_not_found", 404);
    return {
      created,
      item: itemView(row),
      original: { byte_size: row.original_byte_size, file_id: row.original_file_id, sha256: row.original_sha256 },
      release_version: releaseVersion,
      thumbnail: { byte_size: row.thumbnail_byte_size, file_id: row.thumbnail_file_id, sha256: row.thumbnail_sha256 },
    };
  }

  private commitUpload(input: StickerUploadInput & {
    height: number;
    keyDigest: string;
    original: StagedManagedFile;
    requestHash: string;
    thumbnail: StagedManagedFile;
    width: number;
  }) {
    this.assertNewPosition(input.stableId, input.part, input.order);
    const previous = this.currentVersion();
    const releaseVersion = this.nextReleaseVersion();
    if (previous) this.copyRelease(previous, releaseVersion);
    for (const file of [input.original, input.thumbnail]) {
      this.storage.moveStagedFile(file);
      this.database.prepare(`
        INSERT INTO managed_files (file_id, file_kind, owner_ref, relative_path, byte_size, mime_type, sha256, status, created_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, 'committed', ?)
      `).run(file.fileId, file.fileKind, file.ownerRef, file.relativePath, file.bytes, file.mimeType, file.sha256, iso(this.clock()));
    }
    this.database.prepare(`
      INSERT INTO sticker_release_items (
        release_version, stable_id, part, order_index, original_filename, original_relative_path,
        width, height, mime_type, original_sha256, original_file_id, original_byte_size,
        thumbnail_file_id, thumbnail_relative_path, thumbnail_sha256, thumbnail_byte_size, enabled
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `).run(
      releaseVersion, input.stableId, input.part, input.order, input.fileName, input.original.relativePath,
      input.width, input.height, input.expectedMimeType, input.original.sha256, input.original.fileId, input.original.bytes,
      input.thumbnail.fileId, input.thumbnail.relativePath, input.thumbnail.sha256, input.thumbnail.bytes, input.enabled ? 1 : 0,
    );
    this.database.prepare(`
      INSERT OR IGNORE INTO sticker_managed_file_history (
        managed_file_id, stable_id, resource_version, file_kind, created_at
      ) VALUES (?, ?, ?, 'original', ?), (?, ?, ?, 'thumbnail', ?)
    `).run(
      input.original.fileId, input.stableId, releaseVersion, this.clock(),
      input.thumbnail.fileId, input.stableId, releaseVersion, this.clock(),
    );
    this.consumeStagedStorage([input.original, input.thumbnail]);
    this.database.prepare(`
      INSERT INTO sticker_upload_receipts (actor_id, idempotency_key_digest, request_hash, release_version, stable_id, created_at)
      VALUES (?, ?, ?, ?, ?, ?)
    `).run(input.actorId, input.keyDigest, input.requestHash, releaseVersion, input.stableId, iso(this.clock()));
    this.finalizeRelease(releaseVersion, previous, input.actorId);
    this.insertReleaseAudit({
      actorId: input.actorId,
      afterSummary: { enabled: input.enabled, order: input.order, part: input.part, stable_id: input.stableId },
      beforeSummary: previous ? { release_version: previous } : null,
      operationType: "sticker_release_publish",
      releaseVersion,
    });
    return releaseVersion;
  }

  private insertReleaseAudit(input: {
    actorId: string;
    afterSummary: Record<string, unknown>;
    beforeSummary: Record<string, unknown> | null;
    operationType: "sticker_release_publish" | "sticker_release_update";
    releaseVersion: string;
  }) {
    const occurredAt = this.clock();
    this.database.prepare(`
      INSERT INTO admin_operation_logs (
        log_id, actor_type, actor_ref, operation_type, target_type, target_ref,
        result, before_summary, after_summary, occurred_at, expires_at
      ) VALUES (?, 'super_admin', ?, ?, 'sticker_release', ?, 'succeeded', ?, ?, ?, ?)
    `).run(
      randomUUID(), input.actorId, input.operationType, input.releaseVersion,
      serializeAuditSummary(input.beforeSummary), serializeAuditSummary(input.afterSummary),
      occurredAt, occurredAt + auditRetentionMilliseconds,
    );
  }

  private finalizeRelease(releaseVersion: string, previous: string | null, actorId: string) {
    const rows = this.database.prepare(`
      SELECT stable_id, part, order_index, original_sha256, thumbnail_sha256, enabled
      FROM sticker_release_items WHERE release_version = ? ORDER BY stable_id
    `).all(releaseVersion);
    const manifestSha256 = digest(stableJson(rows));
    this.database.prepare(`
      INSERT INTO sticker_releases (release_version, previous_release_version, manifest_sha256, published_at, published_by)
      VALUES (?, ?, ?, ?, ?)
    `).run(releaseVersion, previous, manifestSha256, iso(this.clock()), actorId);
    this.database.prepare(`
      INSERT INTO current_sticker_release (singleton, release_version) VALUES (1, ?)
      ON CONFLICT(singleton) DO UPDATE SET release_version = excluded.release_version
    `).run(releaseVersion);
    const files = this.database.prepare(`
      SELECT original_file_id AS file_id FROM sticker_release_items WHERE release_version = ?
      UNION SELECT thumbnail_file_id AS file_id FROM sticker_release_items WHERE release_version = ?
    `).all(releaseVersion, releaseVersion) as Array<{ file_id: string }>;
    for (const file of files) {
      this.database.prepare(`
        INSERT INTO project_asset_refs (reference_id, managed_file_id, reference_type, created_at)
        VALUES (?, ?, 'release', ?)
      `).run(`release:${releaseVersion}:${file.file_id}`, file.file_id, iso(this.clock()));
    }
  }

  private copyRelease(from: string, to: string) {
    this.database.prepare(`
      INSERT INTO sticker_release_items (
        release_version, stable_id, part, order_index, original_filename, original_relative_path,
        width, height, mime_type, original_sha256, original_file_id, original_byte_size,
        thumbnail_file_id, thumbnail_relative_path, thumbnail_sha256, thumbnail_byte_size, enabled
      )
      SELECT ?, stable_id, part, order_index, original_filename, original_relative_path,
        width, height, mime_type, original_sha256, original_file_id, original_byte_size,
        thumbnail_file_id, thumbnail_relative_path, thumbnail_sha256, thumbnail_byte_size, enabled
      FROM sticker_release_items WHERE release_version = ?
    `).run(to, from);
  }

  private consumeStagedStorage(files: StagedManagedFile[]) {
    const timestamp = iso(this.clock());
    for (const file of files) {
      this.database.prepare(`
        UPDATE storage_reservations SET status = 'consumed', resolved_at = ?
        WHERE operation_id = ? AND status = 'active'
      `).run(timestamp, file.operationId);
    }
    const total = files.reduce((sum, file) => sum + file.bytes, 0);
    const state = this.database.prepare("SELECT managed_content_bytes FROM local_backend_storage_state WHERE singleton = 1").get() as { managed_content_bytes: number };
    const active = this.database.prepare("SELECT COALESCE(SUM(projected_bytes), 0) AS bytes FROM storage_reservations WHERE status = 'active'").get() as { bytes: number };
    const nextBytes = state.managed_content_bytes + total;
    const classification = classifyCapacity(nextBytes, active.bytes);
    this.database.prepare(`
      UPDATE local_backend_storage_state
      SET managed_content_bytes = ?, capacity_notice_level = ?, storage_status = ?, measured_at = ?, version = version + 1
      WHERE singleton = 1
    `).run(nextBytes, classification.capacity_notice_level, classification.storage_status, timestamp);
  }

  private currentVersion() {
    return (this.database.prepare("SELECT release_version FROM current_sticker_release WHERE singleton = 1").get() as { release_version: string } | undefined)?.release_version ?? null;
  }

  private nextReleaseVersion() {
    const date = new Date(this.clock()).toISOString().slice(0, 10).replaceAll("-", "");
    const row = this.database.prepare("SELECT next_sequence FROM sticker_release_sequences WHERE release_date = ?").get(date) as { next_sequence: number } | undefined;
    const sequence = row?.next_sequence ?? 1;
    this.database.prepare(`
      INSERT INTO sticker_release_sequences (release_date, next_sequence) VALUES (?, ?)
      ON CONFLICT(release_date) DO UPDATE SET next_sequence = excluded.next_sequence
    `).run(date, sequence + 1);
    return `asset-${date}.${sequence}`;
  }

  private readItem(releaseVersion: string, stableId: string) {
    return this.database.prepare("SELECT * FROM sticker_release_items WHERE release_version = ? AND stable_id = ?")
      .get(releaseVersion, stableId) as StickerItemRow | undefined;
  }

  private assertNewPosition(stableId: string, part: number, order: number) {
    this.validatePosition(stableId, part, order);
    const current = this.currentVersion();
    if (!current) return;
    if (this.readItem(current, stableId)) throw new StickerReleaseError("sticker_stable_id_conflict", 409);
    const conflict = this.database.prepare(`
      SELECT stable_id FROM sticker_release_items WHERE release_version = ? AND part = ? AND order_index = ?
    `).get(current, part, order);
    if (conflict) throw new StickerReleaseError("sticker_order_conflict", 409);
  }

  private validatePosition(stableId: string, part: number, order: number) {
    const matched = stableId.match(stableIdPattern);
    const numericId = matched ? Number(matched[1]) : Number.NaN;
    if (!matched || !Number.isSafeInteger(numericId) || numericId <= 1_407) throw new StickerReleaseError("sticker_stable_id_invalid");
    if (!Number.isSafeInteger(part) || part < 1 || part > bundledPartCounts.length
      || !Number.isSafeInteger(order) || order <= bundledPartCounts[part - 1]!) {
      throw new StickerReleaseError("sticker_part_order_invalid");
    }
  }

  private validateUpload(input: StickerUploadInput) {
    this.validatePosition(input.stableId, input.part, input.order);
    if (!/^[0-9a-f-]{36}$/i.test(input.actorId) || !idempotencyPattern.test(input.idempotencyKey)
      || !sha256Pattern.test(input.expectedSha256) || !Number.isSafeInteger(input.expectedByteSize)
      || input.expectedByteSize <= 0 || input.expectedByteSize > maximumOriginalBytes
      || !new Set(["image/png", "image/webp"]).has(input.expectedMimeType)) {
      throw new StickerReleaseError("sticker_upload_invalid");
    }
    const expectedExtension = input.expectedMimeType === "image/png" ? ".png" : ".webp";
    if (input.fileName.length > 255 || basename(input.fileName) !== input.fileName || /[\u0000-\u001f]/.test(input.fileName)
      || extname(input.fileName).toLowerCase() !== expectedExtension) throw new StickerReleaseError("sticker_upload_invalid");
  }

  private immediate<T>(action: () => T) {
    this.database.exec("BEGIN IMMEDIATE");
    try {
      const result = action();
      this.database.exec("COMMIT");
      return result;
    } catch (error) {
      if (this.database.inTransaction) this.database.exec("ROLLBACK");
      throw error;
    }
  }

  private migrate() {
    this.database.exec(`
      CREATE TABLE IF NOT EXISTS sticker_release_sequences (
        release_date TEXT PRIMARY KEY,
        next_sequence INTEGER NOT NULL CHECK (next_sequence >= 1)
      );
      CREATE TABLE IF NOT EXISTS sticker_releases (
        release_version TEXT PRIMARY KEY,
        previous_release_version TEXT,
        manifest_sha256 TEXT NOT NULL CHECK (length(manifest_sha256) = 64),
        published_at TEXT NOT NULL,
        published_by TEXT NOT NULL
      );
      CREATE TABLE IF NOT EXISTS sticker_release_items (
        release_version TEXT NOT NULL,
        stable_id TEXT NOT NULL,
        part INTEGER NOT NULL CHECK (part BETWEEN 1 AND 25),
        order_index INTEGER NOT NULL CHECK (order_index > 0),
        original_filename TEXT NOT NULL,
        original_relative_path TEXT NOT NULL,
        width INTEGER NOT NULL CHECK (width > 0),
        height INTEGER NOT NULL CHECK (height > 0),
        mime_type TEXT NOT NULL CHECK (mime_type IN ('image/png', 'image/webp')),
        original_sha256 TEXT NOT NULL CHECK (length(original_sha256) = 64),
        original_file_id TEXT NOT NULL REFERENCES managed_files(file_id),
        original_byte_size INTEGER NOT NULL CHECK (original_byte_size > 0),
        thumbnail_file_id TEXT NOT NULL REFERENCES managed_files(file_id),
        thumbnail_relative_path TEXT NOT NULL,
        thumbnail_sha256 TEXT NOT NULL CHECK (length(thumbnail_sha256) = 64),
        thumbnail_byte_size INTEGER NOT NULL CHECK (thumbnail_byte_size > 0),
        enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
        PRIMARY KEY (release_version, stable_id),
        UNIQUE (release_version, part, order_index)
      );
      CREATE TABLE IF NOT EXISTS current_sticker_release (
        singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
        release_version TEXT NOT NULL
      );
      CREATE TABLE IF NOT EXISTS sticker_upload_receipts (
        actor_id TEXT NOT NULL,
        idempotency_key_digest TEXT NOT NULL CHECK (length(idempotency_key_digest) = 64),
        request_hash TEXT NOT NULL CHECK (length(request_hash) = 64),
        release_version TEXT NOT NULL,
        stable_id TEXT NOT NULL,
        created_at TEXT NOT NULL,
        PRIMARY KEY (actor_id, idempotency_key_digest)
      );
      CREATE TABLE IF NOT EXISTS sticker_managed_file_history (
        managed_file_id TEXT NOT NULL,
        stable_id TEXT NOT NULL,
        resource_version TEXT NOT NULL,
        file_kind TEXT NOT NULL CHECK (file_kind IN ('original', 'thumbnail')),
        created_at INTEGER NOT NULL,
        PRIMARY KEY (managed_file_id, file_kind),
        FOREIGN KEY (managed_file_id) REFERENCES managed_files(file_id)
      );
      CREATE TRIGGER IF NOT EXISTS sticker_releases_no_update
      BEFORE UPDATE ON sticker_releases BEGIN SELECT RAISE(ABORT, 'sticker_releases_immutable'); END;
      CREATE TRIGGER IF NOT EXISTS sticker_releases_no_delete
      BEFORE DELETE ON sticker_releases BEGIN SELECT RAISE(ABORT, 'sticker_releases_immutable'); END;
      CREATE TRIGGER IF NOT EXISTS sticker_release_items_no_update
      BEFORE UPDATE ON sticker_release_items
      WHEN EXISTS (SELECT 1 FROM sticker_releases WHERE release_version = OLD.release_version)
      BEGIN SELECT RAISE(ABORT, 'sticker_release_items_immutable'); END;
      CREATE TRIGGER IF NOT EXISTS sticker_release_items_no_delete
      BEFORE DELETE ON sticker_release_items
      WHEN EXISTS (SELECT 1 FROM sticker_releases WHERE release_version = OLD.release_version)
      BEGIN SELECT RAISE(ABORT, 'sticker_release_items_immutable'); END;
    `);
    this.database.exec(`
      INSERT OR IGNORE INTO sticker_managed_file_history (
        managed_file_id, stable_id, resource_version, file_kind, created_at
      )
      SELECT original_file_id, stable_id, release_version, 'original', strftime('%s', 'now') * 1000
      FROM sticker_release_items;
      INSERT OR IGNORE INTO sticker_managed_file_history (
        managed_file_id, stable_id, resource_version, file_kind, created_at
      )
      SELECT thumbnail_file_id, stable_id, release_version, 'thumbnail', strftime('%s', 'now') * 1000
      FROM sticker_release_items;
    `);
  }
}
