// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReadableDB, WritableDB } from '../Interface.std.ts';
import { sql } from '../util.std.ts';
import type {
  RemoteMegaphoneId,
  RemoteMegaphoneType,
} from '../../types/Megaphone.std.ts';
import { strictAssert } from '../../util/assert.std.ts';

type MegaphoneRow = Readonly<
  Omit<
    RemoteMegaphoneType,
    'isFinished' | 'primaryCtaData' | 'secondaryCtaData'
  > & {
    isFinished: 0 | 1;
    primaryCtaDataJson: string | null;
    secondaryCtaDataJson: string | null;
  }
>;

function megaphoneToRow(megaphone: RemoteMegaphoneType): MegaphoneRow {
  return {
    ...megaphone,
    isFinished: megaphone.isFinished ? 1 : 0,
    primaryCtaDataJson:
      megaphone.primaryCtaData != null
        ? JSON.stringify(megaphone.primaryCtaData)
        : null,
    secondaryCtaDataJson:
      megaphone.secondaryCtaData != null
        ? JSON.stringify(megaphone.secondaryCtaData)
        : null,
  };
}

function rowToMegaphone(megaphoneRow: MegaphoneRow): RemoteMegaphoneType {
  return {
    ...megaphoneRow,
    isFinished: megaphoneRow.isFinished === 1,
    primaryCtaData:
      megaphoneRow.primaryCtaDataJson != null
        ? JSON.parse(megaphoneRow.primaryCtaDataJson)
        : null,
    secondaryCtaData:
      megaphoneRow.secondaryCtaDataJson != null
        ? JSON.parse(megaphoneRow.secondaryCtaDataJson)
        : null,
  };
}

export function hasMegaphone(
  db: ReadableDB,
  megaphoneId: RemoteMegaphoneId
): boolean {
  const [query, params] = sql`
    SELECT EXISTS (
      SELECT 1 FROM megaphones
      WHERE id = ${megaphoneId}
    )
  `;
  const result = db.prepare(query, { pluck: true }).get<number>(params);
  return result === 1;
}

export function getAllMegaphones(
  db: ReadableDB
): ReadonlyArray<RemoteMegaphoneType> {
  const [query, params] = sql`
    SELECT * FROM megaphones
  `;
  return db
    .prepare(query)
    .all<MegaphoneRow>(params)
    .map(row => rowToMegaphone(row));
}

export function getAllMegaphoneIds(
  db: ReadableDB
): ReadonlyArray<RemoteMegaphoneId> {
  const [query, params] = sql`
    SELECT id FROM megaphones
  `;
  return db.prepare(query, { pluck: true }).all<RemoteMegaphoneId>(params);
}

function _insertMegaphone(
  db: WritableDB,
  megaphone: RemoteMegaphoneType
): void {
  const row = megaphoneToRow(megaphone);
  const [query, params] = sql`
    INSERT INTO megaphones (
      id,
      desktopMinVersion,
      priority,
      dontShowBeforeEpochMs,
      dontShowAfterEpochMs,
      showForNumberOfDays,
      primaryCtaId,
      secondaryCtaId,
      primaryCtaDataJson,
      secondaryCtaDataJson,
      conditionalId,
      title,
      body,
      primaryCtaText,
      secondaryCtaText,
      imagePath,
      localeFetched,
      shownAt,
      snoozedAt,
      snoozeCount,
      isFinished
    ) VALUES (
      ${row.id},
      ${row.desktopMinVersion},
      ${row.priority},
      ${row.dontShowBeforeEpochMs},
      ${row.dontShowAfterEpochMs},
      ${row.showForNumberOfDays},
      ${row.primaryCtaId},
      ${row.secondaryCtaId},
      ${row.primaryCtaDataJson},
      ${row.secondaryCtaDataJson},
      ${row.conditionalId},
      ${row.title},
      ${row.body},
      ${row.primaryCtaText},
      ${row.secondaryCtaText},
      ${row.imagePath},
      ${row.localeFetched},
      ${row.shownAt},
      ${row.snoozedAt},
      ${row.snoozeCount},
      ${row.isFinished}
    )
  `;
  db.prepare(query).run(params);
}

export function createMegaphone(
  db: WritableDB,
  megaphone: RemoteMegaphoneType
): void {
  return db.transaction(() => {
    _insertMegaphone(db, megaphone);
  })();
}

export function updateMegaphone(
  db: WritableDB,
  megaphone: RemoteMegaphoneType
): void {
  const row = megaphoneToRow(megaphone);
  const [query, params] = sql`
    UPDATE megaphones
    SET
      desktopMinVersion = ${row.desktopMinVersion},
      priority = ${row.priority},
      dontShowBeforeEpochMs = ${row.dontShowBeforeEpochMs},
      dontShowAfterEpochMs = ${row.dontShowAfterEpochMs},
      showForNumberOfDays = ${row.showForNumberOfDays},
      primaryCtaId = ${row.primaryCtaId},
      secondaryCtaId = ${row.secondaryCtaId},
      primaryCtaDataJson = ${row.primaryCtaDataJson},
      secondaryCtaDataJson = ${row.secondaryCtaDataJson},
      conditionalId = ${row.conditionalId},
      title = ${row.title},
      body = ${row.body},
      primaryCtaText = ${row.primaryCtaText},
      secondaryCtaText = ${row.secondaryCtaText},
      imagePath = ${row.imagePath},
      localeFetched = ${row.localeFetched},
      shownAt = ${row.shownAt},
      snoozedAt = ${row.snoozedAt},
      snoozeCount = ${row.snoozeCount},
      isFinished = ${row.isFinished}
    WHERE
      id = ${row.id}
  `;
  db.prepare(query).run(params);
}

export function finishMegaphone(
  db: WritableDB,
  megaphoneId: RemoteMegaphoneId
): void {
  const [query, params] = sql`
    UPDATE megaphones
    SET isFinished = 1
    WHERE id = ${megaphoneId}
  `;
  const result = db.prepare(query).run(params);
  strictAssert(
    result.changes === 1,
    `finishMegaphone: Expected changes: 1, Actual: ${result.changes}`
  );
}

export function snoozeMegaphone(
  db: WritableDB,
  megaphoneId: RemoteMegaphoneId
): void {
  const [query, params] = sql`
    UPDATE megaphones
    SET
      snoozedAt = ${Date.now()},
      snoozeCount = snoozeCount + 1
    WHERE id = ${megaphoneId}
  `;
  const result = db.prepare(query).run(params);
  strictAssert(
    result.changes === 1,
    `snoozeMegaphone: Expected changes: 1, Actual: ${result.changes}`
  );
}

// Note: When deleting megaphones, ensure megaphones are removed from redux also.
// See: deleteMegaphoneAndRemoveFromRedux()
export function deleteMegaphone(
  db: WritableDB,
  megaphoneId: RemoteMegaphoneId
): void {
  const [query, params] = sql`
    DELETE FROM megaphones
    WHERE id = ${megaphoneId}
  `;
  const result = db.prepare(query).run(params);
  strictAssert(
    result.changes === 1,
    `deleteMegaphone: Expected changes: 1, Actual: ${result.changes}`
  );
}

export function internalDeleteAllMegaphones(db: WritableDB): number {
  const [query, params] = sql`
    DELETE FROM megaphones
  `;
  const result = db.prepare(query).run(params);
  return result.changes;
}

export function getAllMegaphoneImageLocalPaths(db: ReadableDB): Set<string> {
  const localPaths = db
    .prepare('SELECT imagePath FROM megaphones WHERE imagePath IS NOT NULL', {
      pluck: true,
    })
    .all<string>();
  return new Set(localPaths);
}
