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

import { pathToFileURL } from 'node:url';
import { app, autoUpdater, shell } from 'electron';
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';

import { Updater, createTempDir, deleteTempDir } from './common.main.ts';
import { explodePromise } from '../util/explodePromise.std.ts';
import { isProduction } from '../util/version.std.ts';
import * as Errors from '../types/errors.std.ts';
import { DialogType } from '../types/Dialogs.std.ts';

const APP_ID = '1230208093';

export class MacOSUpdater extends Updater {
  protected async deletePreviousInstallers(): Promise<void> {
    // No installers are cache on macOS
  }

  protected async installUpdate(
    updateFilePath: string
  ): Promise<() => Promise<void>> {
    const { logger } = this;

    logger.info('downloadAndInstall: handing download to electron...');
    try {
      await this.#handToAutoUpdate(updateFilePath);
    } catch (error) {
      const readOnly = 'Cannot update while running on a read-only volume';
      const message: string = error.message || '';
      this.markCannotUpdate(
        error,
        message.includes(readOnly)
          ? DialogType.MacOS_Read_Only
          : DialogType.Cannot_Update
      );

      throw error;
    }

    // At this point, closing the app will cause the update to be installed automatically
    //   because Squirrel has cached the update file and will do the right thing.

    return async () => {
      logger.info('downloadAndInstall: restarting...');
      this.markRestarting();
      autoUpdater.quitAndInstall();
    };
  }

  protected handleUpdateFromThirdParty(version: string): boolean {
    if (!process.mas) {
      return false;
    }

    const { logger } = this;

    this.setUpdateListener(async () => {
      logger.info('handleUpdateFromThirdParty: clicked');

      if (isProduction(app.getVersion())) {
        // App Store
        await shell.openExternal(
          `itms-apps://itunes.apple.com/app/apple-store/${APP_ID}`
        );
      } else {
        // Test Flight
        await shell.openExternal(
          `itms-beta://beta.itunes.apple.com/v1/app/${APP_ID}`
        );
      }
    });

    const mainWindow = this.getMainWindow();
    if (!mainWindow) {
      logger.warn(
        'handleUpdateFromThirdParty: no mainWindow, cannot show update dialog'
      );
      return true;
    }

    logger.info(`handleUpdateFromThirdParty: offering update to ${version}`);
    mainWindow.webContents.send('show-update-dialog', DialogType.MASUpdate);

    return true;
  }

  async #handToAutoUpdate(filePath: string): Promise<void> {
    const { logger } = this;
    const { promise, resolve, reject } = explodePromise<void>();

    const onError = (...args: Array<unknown>) => {
      logger.error('autoUpdater: error', ...args.map(Errors.toLogFormat));

      const [error] = args;
      cleanup();
      reject(error);
    };

    const onDownloaded = () => {
      logger.info('autoUpdater: update-downloaded event fired');
      cleanup();
      resolve();
    };

    function cleanup() {
      autoUpdater.removeListener('error', onError);
      autoUpdater.removeListener('update-downloaded', onDownloaded);
    }

    autoUpdater.on('error', onError);
    autoUpdater.on('update-downloaded', onDownloaded);

    // See: https://github.com/electron/electron/issues/5020#issuecomment-477636990
    const updateUrl = pathToFileURL(filePath).href;

    const tempDir = await createTempDir();
    try {
      const feedPath = join(tempDir, 'feed.json');
      await writeFile(
        feedPath,
        JSON.stringify({
          url: updateUrl,
        })
      );

      autoUpdater.setFeedURL({
        url: pathToFileURL(feedPath).href,
        serverType: 'json',
      });
      autoUpdater.checkForUpdates();

      await promise;
    } finally {
      await deleteTempDir(this.logger, tempDir);
    }
  }
}
