import { readFileSync, writeFileSync } from 'node:fs';
import { basename, extname, resolve } from 'node:path';

import { getOctokit } from '@actions/github';

import { uploadAssets } from './upload-release-assets';
import { getAssetName } from './utils';

import type { Artifact, TargetInfo } from './types';
import { createArtifact } from './utils';

type Platform = {
  signature: string;
  url: string;
};

type VersionContent = {
  version: string;
  notes: string;
  pub_date: string;
  platforms: {
    [key: string]: Platform;
  };
};

export async function uploadVersionJSON(
  owner: string,
  repo: string,
  version: string,
  notes: string,
  tagName: string,
  releaseId: number,
  artifacts: Artifact[],
  targetInfo: TargetInfo,
  unzippedSig: boolean,
  updaterJsonPreferNsis: boolean,
  updaterJsonKeepUniversal: boolean,
  retryAttempts: number,
  assetNamePattern?: string,
) {
  if (process.env.GITHUB_TOKEN === undefined) {
    throw new Error('GITHUB_TOKEN is required');
  }

  const github = getOctokit(process.env.GITHUB_TOKEN);

  const versionFilename = 'latest.json';
  const versionFile = resolve(process.cwd(), versionFilename);
  const versionContent: VersionContent = {
    version,
    notes,
    pub_date: new Date().toISOString(),
    platforms: {},
  };

  const assets = await github.rest.repos.listReleaseAssets({
    owner: owner,
    repo: repo,
    release_id: releaseId,
    per_page: 50,
  });
  const asset = assets.data.find((e) => e.name === versionFilename);

  if (asset) {
    const assetData = (
      await github.request(
        'GET /repos/{owner}/{repo}/releases/assets/{asset_id}',
        {
          owner: owner,
          repo: repo,
          asset_id: asset.id,
          headers: {
            accept: 'application/octet-stream',
          },
        },
      )
    ).data as unknown as ArrayBuffer;

    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
    versionContent.platforms = JSON.parse(
      Buffer.from(assetData).toString(),
      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    ).platforms;
  }

  const downloadUrls = new Map<string, string>();
  for (const data of assets.data) {
    downloadUrls.set(data.name, data.browser_download_url);
  }

  // Assets matching artifacts generated by this action
  const filteredAssets = [];
  for (const artifact of artifacts) {
    const assetName = getAssetName(artifact, assetNamePattern)
      .trim()
      .replace(/[ ()[\]{}]/g, '.')
      .replace(/\.\./g, '.')
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '');
    const downloadUrl = downloadUrls.get(assetName);
    if (downloadUrl) {
      filteredAssets.push({
        downloadUrl,
        assetName,
        path: artifact.path,
        arch: artifact.arch,
        bundle: artifact.bundle,
      });
    }
  }

  const signatureFiles = filteredAssets.filter((asset) => {
    return asset.assetName.endsWith('.sig');
  });
  function signaturePriority(signaturePath: string) {
    if (
      (unzippedSig && signaturePath.endsWith('.AppImage.sig')) ||
      (!unzippedSig && signaturePath.endsWith('.AppImage.tar.gz.sig'))
    ) {
      return 100;
    }
    const priorities = updaterJsonPreferNsis
      ? unzippedSig
        ? ['.exe.sig', '.msi.sig']
        : ['.nsis.zip.sig', '.msi.zip.sig']
      : unzippedSig
        ? ['.msi.sig', '.exe.sig']
        : ['.msi.zip.sig', '.nsis.zip.sig'];
    for (const [index, extension] of priorities.entries()) {
      if (signaturePath.endsWith(extension)) {
        return 100 - index;
      }
    }
    return 0;
  }
  signatureFiles.sort((a, b) => {
    return signaturePriority(b.path) - signaturePriority(a.path);
  });

  if (!signatureFiles[0]) {
    console.warn(
      'Signature not found for the updater JSON. Skipping upload...',
    );
    return;
  }

  for (const [idx, signatureFile] of signatureFiles.entries()) {
    const updaterFileName = basename(
      signatureFile.assetName,
      extname(signatureFile.assetName),
    );
    let updaterFileDownloadUrl = filteredAssets.find(
      (asset) => asset.assetName === updaterFileName,
    )?.downloadUrl;

    if (!updaterFileDownloadUrl) {
      console.warn(
        `Updater asset belonging to signature file "${signatureFile.assetName}" not found.`,
      );
      continue;
    }

    // Untagged release downloads won't work after the release was published
    updaterFileDownloadUrl = updaterFileDownloadUrl.replace(
      /\/download\/(untagged-[^/]+)\//,
      tagName ? `/download/${tagName}/` : '/latest/download/',
    );

    let os = targetInfo.platform as string;
    if (os === 'macos') {
      os = 'darwin';
    }

    let arch = signatureFile.arch;
    arch =
      arch === 'amd64' || arch === 'x86_64' || arch === 'x64'
        ? 'x86_64'
        : arch === 'x86' || arch === 'i386'
          ? 'i686'
          : arch === 'arm'
            ? 'armv7'
            : arch === 'arm64'
              ? 'aarch64'
              : arch;

    // This is our primary updater type we use for `{os}-{arch}`
    if (idx === 0) {
      if (os === 'darwin' && arch === 'universal') {
        // Don't overwrite native builds
        if (!versionContent.platforms['darwin-aarch64']) {
          (versionContent.platforms['darwin-aarch64'] as unknown) = {
            signature: readFileSync(signatureFile.path).toString(),
            url: updaterFileDownloadUrl,
          };
        }
        if (!versionContent.platforms['darwin-x86_64']) {
          (versionContent.platforms['darwin-x86_64'] as unknown) = {
            signature: readFileSync(signatureFile.path).toString(),
            url: updaterFileDownloadUrl,
          };
        }
      }
      if (updaterJsonKeepUniversal || os !== 'darwin' || arch !== 'universal') {
        (versionContent.platforms[`${os}-${arch}`] as unknown) = {
          signature: readFileSync(signatureFile.path).toString(),
          url: updaterFileDownloadUrl,
        };
      }
    }

    // This is for the new `{os}-{arch}-{installer}` format
    if (os === 'darwin' && arch === 'universal') {
      // Don't overwrite native builds
      if (!versionContent.platforms['darwin-aarch64-app']) {
        (versionContent.platforms['darwin-aarch64-app'] as unknown) = {
          signature: readFileSync(signatureFile.path).toString(),
          url: updaterFileDownloadUrl,
        };
      }
      if (!versionContent.platforms['darwin-x86_64-app']) {
        (versionContent.platforms['darwin-x86_64-app'] as unknown) = {
          signature: readFileSync(signatureFile.path).toString(),
          url: updaterFileDownloadUrl,
        };
      }
    }
    if (updaterJsonKeepUniversal || os !== 'darwin' || arch !== 'universal') {
      (versionContent.platforms[
        `${os}-${arch}-${signatureFile.bundle}`
      ] as unknown) = {
        signature: readFileSync(signatureFile.path).toString(),
        url: updaterFileDownloadUrl,
      };
    }
  }

  writeFileSync(versionFile, JSON.stringify(versionContent, null, 2));

  if (asset) {
    // https://docs.github.com/en/rest/releases/assets#update-a-release-asset
    await github.rest.repos.deleteReleaseAsset({
      owner: owner,
      repo: repo,
      release_id: releaseId,
      asset_id: asset.id,
    });
  }

  const artifact = createArtifact({
    path: versionFile,
    name: versionFilename,
    debug: false,
    platform: targetInfo.platform,
    arch: '',
    bundle: '',
    version,
  });

  await uploadAssets(owner, repo, releaseId, [artifact], retryAttempts);
}
