// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import {
  readAndDecryptDataFromDisk,
  writeNewAttachmentData as doWriteNewAttachmentData,
  createDeleter,
} from '../../app/attachments.node.ts';
import {
  createAbsolutePathGetter,
  createPlaintextReader,
  createWriterForNew,
  createDoesExist,
  getUnusedFilename,
  saveAttachmentToDisk,
  copyIntoAttachmentsDirectory,
} from '../windows/main/attachments.preload.ts';
import {
  getImageDimensions,
  makeImageThumbnail,
  makeObjectUrl,
  makeVideoScreenshot,
  revokeObjectUrl,
} from '../types/VisualAttachment.dom.ts';
import { loadData } from './Attachment.std.ts';
import {
  loadContactData as doLoadContactData,
  loadPreviewData as doLoadPreviewData,
  loadQuoteData as doLoadQuoteData,
  loadStickerData as doLoadStickerData,
  processNewAttachment as doProcessNewAttachment,
  processNewSticker as doProcessNewSticker,
  upgradeSchema,
} from '../types/Message2.preload.ts';
import type {
  AttachmentType,
  AddressableAttachmentType,
  LocalAttachmentV2Type,
} from '../types/Attachment.std.ts';
import type { MessageAttachmentType } from '../types/AttachmentDownload.std.ts';
import type { MessageAttributesType } from '../model-types.d.ts';
import { createLogger } from '../logging/log.std.ts';
import { itemStorage } from '../textsecure/Storage.preload.ts';
import {
  ATTACHMENTS_PATH,
  STICKERS_PATH,
  BADGES_PATH,
  DRAFT_PATH,
  TEMP_PATH,
  AVATARS_PATH,
  DOWNLOADS_PATH,
  MEGAPHONES_PATH,
} from './basePaths.preload.ts';
import { DataReader } from '../sql/Client.preload.ts';
import { getExistingAttachmentDataForReuse } from './attachments/deduplicateAttachment.preload.ts';
import { getPlaintextHashForInMemoryAttachment } from '../AttachmentCrypto.node.ts';

const logger = createLogger('migrations');

type EncryptedReader = (
  attachment: Partial<AddressableAttachmentType>
) => Promise<Uint8Array<ArrayBuffer>>;

type EncryptedWriter = (
  data: Uint8Array<ArrayBuffer>
) => Promise<LocalAttachmentV2Type>;

function createEncryptedReader(basePath: string): EncryptedReader {
  const fallbackReader = createPlaintextReader(basePath);
  const pathGetter = createAbsolutePathGetter(basePath);

  return async (
    attachment: Partial<AddressableAttachmentType>
  ): Promise<Uint8Array<ArrayBuffer>> => {
    // In-memory
    if (attachment.data != null) {
      return attachment.data;
    }

    if (attachment.path == null) {
      throw new Error('Attachment was not downloaded yet');
    }

    if (attachment.version !== 2) {
      return fallbackReader(attachment.path);
    }

    if (attachment.localKey == null || attachment.size == null) {
      throw new Error('Failed to decrypt v2 attachment');
    }

    const absolutePath = pathGetter(attachment.path);

    return readAndDecryptDataFromDisk({
      absolutePath,
      keysBase64: attachment.localKey,
      size: attachment.size,
    });
  };
}

function createEncryptedWriterForNew(basePath: string): EncryptedWriter {
  const pathGetter = createAbsolutePathGetter(basePath);

  return data =>
    doWriteNewAttachmentData({
      data,
      getAbsoluteAttachmentPath: pathGetter,
    });
}

export const readAttachmentData = createEncryptedReader(ATTACHMENTS_PATH);
export const loadAttachmentData = loadData(readAttachmentData);
export const loadContactData = doLoadContactData(loadAttachmentData);
export const loadPreviewData = doLoadPreviewData(loadAttachmentData);
export const loadQuoteData = doLoadQuoteData(loadAttachmentData);
export const loadStickerData = doLoadStickerData(loadAttachmentData);
export const getAbsoluteAttachmentPath =
  createAbsolutePathGetter(ATTACHMENTS_PATH);

const __DANGEROUS__deleteAttachmentFile = createDeleter(ATTACHMENTS_PATH);
export const maybeDeleteAttachmentFile = async (
  relativePath: string
): Promise<{ wasDeleted: boolean }> => {
  const isSafeToDelete =
    await DataReader.isAttachmentSafeToDelete(relativePath);

  if (!isSafeToDelete) {
    return { wasDeleted: false };
  }

  await __DANGEROUS__deleteAttachmentFile(relativePath);
  return { wasDeleted: true };
};

export const writeNewAttachmentData =
  createEncryptedWriterForNew(ATTACHMENTS_PATH);
export const doesAttachmentExist = createDoesExist(ATTACHMENTS_PATH);

export const writeNewStickerData = createEncryptedWriterForNew(STICKERS_PATH);
export const deleteSticker = createDeleter(STICKERS_PATH);
export const readStickerData = createEncryptedReader(STICKERS_PATH);

export const getAbsoluteBadgeImageFilePath =
  createAbsolutePathGetter(BADGES_PATH);
export const writeNewBadgeImageFileData = createWriterForNew(
  BADGES_PATH,
  '.svg'
);

export const getAbsoluteMegaphoneImageFilePath =
  createAbsolutePathGetter(MEGAPHONES_PATH);
export const writeNewMegaphoneImageFileData =
  createWriterForNew(MEGAPHONES_PATH);

export const getAbsoluteTempPath = createAbsolutePathGetter(TEMP_PATH);
const writeNewTempData = createEncryptedWriterForNew(TEMP_PATH);
export const writeNewPlaintextTempData = createWriterForNew(TEMP_PATH);
export const deleteTempFile = createDeleter(TEMP_PATH);
export const readTempData = createEncryptedReader(TEMP_PATH);
export const copyAttachmentIntoTempDirectory = copyIntoAttachmentsDirectory({
  sourceDir: ATTACHMENTS_PATH,
  targetDir: TEMP_PATH,
});

export const getAbsoluteDraftPath = createAbsolutePathGetter(DRAFT_PATH);
export const writeNewDraftData = createEncryptedWriterForNew(DRAFT_PATH);
export const deleteDraftFile = createDeleter(DRAFT_PATH);
export const readDraftData = createEncryptedReader(DRAFT_PATH);

export const getAbsoluteDownloadsPath =
  createAbsolutePathGetter(DOWNLOADS_PATH);
export const deleteDownloadFile = createDeleter(DOWNLOADS_PATH);

export const readAvatarData = createEncryptedReader(AVATARS_PATH);
export const writeNewAvatarData = createEncryptedWriterForNew(AVATARS_PATH);
export const deleteAvatar = createDeleter(AVATARS_PATH);

export const processNewAttachment = (
  attachment: AttachmentType,
  attachmentType: MessageAttachmentType
): ReturnType<typeof doProcessNewAttachment> =>
  doProcessNewAttachment(attachment, attachmentType, {
    writeNewAttachmentData,
    makeObjectUrl,
    revokeObjectUrl,
    getImageDimensions,
    makeImageThumbnail,
    makeVideoScreenshot,
    logger,
  });
export const processNewSticker = (
  stickerData: Uint8Array<ArrayBuffer>
): ReturnType<typeof doProcessNewSticker> =>
  doProcessNewSticker(stickerData, false, {
    writeNewStickerData,
    getImageDimensions,
    logger,
  });
export const processNewEphemeralSticker = (
  stickerData: Uint8Array<ArrayBuffer>
): ReturnType<typeof doProcessNewSticker> =>
  doProcessNewSticker(stickerData, true, {
    writeNewStickerData: writeNewTempData,
    getImageDimensions,
    logger,
  });

export const upgradeMessageSchema = (
  message: MessageAttributesType,
  options: { maxVersion?: number } = {}
): Promise<MessageAttributesType> => {
  const { maxVersion } = options;

  return upgradeSchema(message, {
    maybeDeleteAttachmentFile,
    doesAttachmentExist,
    getExistingAttachmentDataForReuse,
    getImageDimensions,
    getPlaintextHashForInMemoryAttachment,
    getRegionCode: () => itemStorage.get('regionCode'),
    makeImageThumbnail,
    makeObjectUrl,
    makeVideoScreenshot,
    readAttachmentData,
    revokeObjectUrl,
    writeNewAttachmentData,
    writeNewStickerData,
    logger,
    maxVersion,
  });
};

export { getUnusedFilename, saveAttachmentToDisk };
