import { promises as fs } from "node:fs";
import path from "node:path";

const DEFAULT_RELEASE_DIRECTORY =
  "/var/www/front/official-mail-downloads/windows";
const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
const SHA256_PATTERN = /^[a-f0-9]{64}$/i;
const INSTALLER_ARTIFACT_PATTERN =
  /^OfficialMail-[0-9A-Za-z][0-9A-Za-z._+-]*\.(?:exe|msix|msixbundle)$/;
const PACKAGE_ARTIFACT_PATTERN =
  /^OfficialMailPackage-[0-9A-Za-z][0-9A-Za-z._+-]*\.zip$/;

export type WindowsUpdatePackage = {
  type: "zip";
  artifactFile: string;
  sha256: string;
  fileSize: number;
};

export type WindowsReleaseManifest = {
  platform: "windows";
  channel: "stable" | "beta";
  version: string;
  build: number;
  minimumVersion: string;
  mandatory: boolean;
  installerType: "exe" | "msix" | "msixbundle";
  artifactFile: string;
  sha256: string;
  fileSize: number;
  releaseNotes: string;
  publishedAt: string;
  package?: WindowsUpdatePackage;
};

function requiredString(
  source: Record<string, unknown>,
  key: string,
): string {
  const value = source[key];
  if (typeof value !== "string" || value.trim() === "") {
    throw new Error(`Windows release manifest field '${key}' is invalid.`);
  }
  return value.trim();
}

function parseManifest(source: unknown): WindowsReleaseManifest {
  if (!source || typeof source !== "object" || Array.isArray(source)) {
    throw new Error("Windows release manifest must be a JSON object.");
  }

  const record = source as Record<string, unknown>;
  const version = requiredString(record, "version");
  const minimumVersion = requiredString(record, "minimumVersion");
  const artifactFile = requiredString(record, "artifactFile");
  const sha256 = requiredString(record, "sha256").toLowerCase();
  const releaseNotes = requiredString(record, "releaseNotes");
  const publishedAt = requiredString(record, "publishedAt");
  const build = record.build;
  const fileSize = record.fileSize;
  const packageValue = record.package;

  if (record.platform !== "windows") {
    throw new Error("Windows release manifest platform must be 'windows'.");
  }
  if (record.channel !== "stable" && record.channel !== "beta") {
    throw new Error("Windows release manifest channel is invalid.");
  }
  if (!VERSION_PATTERN.test(version) || !VERSION_PATTERN.test(minimumVersion)) {
    throw new Error("Windows release manifest version is invalid.");
  }
  if (!Number.isSafeInteger(build) || Number(build) < 1) {
    throw new Error("Windows release manifest build is invalid.");
  }
  if (typeof record.mandatory !== "boolean") {
    throw new Error("Windows release manifest mandatory flag is invalid.");
  }
  if (
    record.installerType !== "exe" &&
    record.installerType !== "msix" &&
    record.installerType !== "msixbundle"
  ) {
    throw new Error("Windows release manifest installer type is invalid.");
  }
  if (!INSTALLER_ARTIFACT_PATTERN.test(artifactFile) || path.basename(artifactFile) !== artifactFile) {
    throw new Error("Windows release manifest artifact filename is invalid.");
  }
  if (!artifactFile.endsWith(`.${record.installerType}`)) {
    throw new Error("Windows release manifest artifact extension does not match.");
  }
  if (!SHA256_PATTERN.test(sha256)) {
    throw new Error("Windows release manifest SHA-256 is invalid.");
  }
  if (!Number.isSafeInteger(fileSize) || Number(fileSize) < 1) {
    throw new Error("Windows release manifest file size is invalid.");
  }
  if (Number.isNaN(Date.parse(publishedAt))) {
    throw new Error("Windows release manifest publishedAt is invalid.");
  }

  let updatePackage: WindowsUpdatePackage | undefined;
  if (packageValue !== undefined) {
    if (!packageValue || typeof packageValue !== "object" || Array.isArray(packageValue)) {
      throw new Error("Windows update package must be a JSON object.");
    }
    const packageRecord = packageValue as Record<string, unknown>;
    const packageFile = requiredString(packageRecord, "artifactFile");
    const packageSha256 = requiredString(packageRecord, "sha256").toLowerCase();
    const packageSize = packageRecord.fileSize;
    if (packageRecord.type !== "zip") {
      throw new Error("Windows update package type is invalid.");
    }
    if (!PACKAGE_ARTIFACT_PATTERN.test(packageFile) || path.basename(packageFile) !== packageFile) {
      throw new Error("Windows update package filename is invalid.");
    }
    if (!SHA256_PATTERN.test(packageSha256)) {
      throw new Error("Windows update package SHA-256 is invalid.");
    }
    if (!Number.isSafeInteger(packageSize) || Number(packageSize) < 1) {
      throw new Error("Windows update package file size is invalid.");
    }
    updatePackage = {
      type: "zip",
      artifactFile: packageFile,
      sha256: packageSha256,
      fileSize: Number(packageSize),
    };
  }

  return {
    platform: "windows",
    channel: record.channel,
    version,
    build: Number(build),
    minimumVersion,
    mandatory: record.mandatory,
    installerType: record.installerType,
    artifactFile,
    sha256,
    fileSize: Number(fileSize),
    releaseNotes,
    publishedAt,
    package: updatePackage,
  };
}

export function getWindowsReleaseDirectory() {
  return (
    process.env.OFFICIAL_MAIL_WINDOWS_RELEASE_DIR?.trim() ||
    DEFAULT_RELEASE_DIRECTORY
  );
}

export function getWindowsArtifactPath(fileName: string) {
  if (
    (!INSTALLER_ARTIFACT_PATTERN.test(fileName) &&
      !PACKAGE_ARTIFACT_PATTERN.test(fileName)) ||
    path.basename(fileName) !== fileName
  ) {
    return null;
  }
  return path.join(getWindowsReleaseDirectory(), fileName);
}

export async function readWindowsReleaseManifest(
  channel: "stable" | "beta",
) {
  const manifestPath = path.join(
    getWindowsReleaseDirectory(),
    `${channel}.json`,
  );
  const manifest = parseManifest(
    JSON.parse(await fs.readFile(manifestPath, "utf8")),
  );

  if (manifest.channel !== channel) {
    throw new Error("Windows release manifest channel does not match.");
  }

  const artifactPath = getWindowsArtifactPath(manifest.artifactFile);
  if (!artifactPath) {
    throw new Error("Windows release artifact path is invalid.");
  }
  const artifact = await fs.stat(artifactPath);
  if (!artifact.isFile() || artifact.size !== manifest.fileSize) {
    throw new Error("Windows release artifact is missing or incomplete.");
  }

  if (manifest.package) {
    const packagePath = getWindowsArtifactPath(manifest.package.artifactFile);
    if (!packagePath) {
      throw new Error("Windows update package path is invalid.");
    }
    const packageArtifact = await fs.stat(packagePath);
    if (!packageArtifact.isFile() || packageArtifact.size !== manifest.package.fileSize) {
      throw new Error("Windows update package is missing or incomplete.");
    }
  }

  return manifest;
}
