import "server-only";

import type { RowDataPacket } from "mysql2/promise";

import { ensureOfficialMailSchema, getDbPool } from "@/lib/db";
import { getMailAbsoluteUrl } from "@/lib/mail-urls";

export const APP_PLATFORMS = ["android", "windows-store", "windows-exe"] as const;
export type AppPlatform = (typeof APP_PLATFORMS)[number];
export type AppUpdateMode = "recommended" | "required";

export type AppVersionPolicy = {
  platform: AppPlatform;
  latestVersion: string;
  latestBuild: number;
  minimumVersion: string;
  minimumBuild: number;
  updateMode: AppUpdateMode;
  releaseNotes: string;
  updatedAt: string | null;
};

type PolicyRow = RowDataPacket & {
  platform: AppPlatform;
  latest_version: string;
  latest_build: number;
  minimum_version: string;
  minimum_build: number;
  update_mode: AppUpdateMode;
  release_notes: string;
  updated_at: Date | string;
};

const VERSION_PATTERN = /^\d+\.\d+\.\d+$/;

export function isAppPlatform(value: unknown): value is AppPlatform {
  return APP_PLATFORMS.includes(value as AppPlatform);
}

export function compareAppBuild(
  firstVersion: string,
  firstBuild: number,
  secondVersion: string,
  secondBuild: number,
) {
  const first = firstVersion.split(".").map(Number);
  const second = secondVersion.split(".").map(Number);
  for (let index = 0; index < 3; index += 1) {
    if (first[index] !== second[index]) return first[index] > second[index] ? 1 : -1;
  }
  return Math.sign(firstBuild - secondBuild);
}

function defaultPolicy(platform: AppPlatform): AppVersionPolicy {
  return {
    platform,
    latestVersion: "0.0.0",
    latestBuild: 0,
    minimumVersion: "0.0.0",
    minimumBuild: 0,
    updateMode: "recommended",
    releaseNotes: "",
    updatedAt: null,
  };
}

function mapPolicy(row: PolicyRow): AppVersionPolicy {
  return {
    platform: row.platform,
    latestVersion: row.latest_version,
    latestBuild: row.latest_build,
    minimumVersion: row.minimum_version,
    minimumBuild: row.minimum_build,
    updateMode: row.update_mode,
    releaseNotes: row.release_notes,
    updatedAt: row.updated_at instanceof Date ? row.updated_at.toISOString() : String(row.updated_at),
  };
}

export async function getAppVersionPolicies() {
  await ensureOfficialMailSchema();
  const [rows] = await getDbPool().query<PolicyRow[]>(
    "SELECT * FROM app_version_policies WHERE platform IN ('android', 'windows-store', 'windows-exe')",
  );
  const byPlatform = new Map(rows.map((row) => [row.platform, mapPolicy(row)]));
  return APP_PLATFORMS.map((platform) => byPlatform.get(platform) ?? defaultPolicy(platform));
}

export async function getAppVersionPolicy(platform: AppPlatform) {
  const policies = await getAppVersionPolicies();
  return policies.find((policy) => policy.platform === platform) ?? defaultPolicy(platform);
}

export function getAppUpdateUrl(platform: AppPlatform) {
  switch (platform) {
    case "android":
      return "https://play.google.com/store/apps/details?id=kr.officialsite.official_mail";
    case "windows-store":
      return "https://apps.microsoft.com/detail/9NCD8ZQFP0NJ";
    case "windows-exe":
      return getMailAbsoluteUrl("/api/app/releases/windows/download/latest?channel=stable");
  }
}

export function getAppUpdateDecision(
  policy: AppVersionPolicy,
  currentVersion: string,
  currentBuild: number,
): "none" | AppUpdateMode {
  if (!VERSION_PATTERN.test(currentVersion) || !Number.isSafeInteger(currentBuild) || currentBuild < 0) {
    return "required";
  }
  if (compareAppBuild(currentVersion, currentBuild, policy.minimumVersion, policy.minimumBuild) < 0) {
    return "required";
  }
  if (compareAppBuild(currentVersion, currentBuild, policy.latestVersion, policy.latestBuild) < 0) {
    return policy.updateMode;
  }
  return "none";
}

export async function saveAppVersionPolicy(
  input: Omit<AppVersionPolicy, "updatedAt">,
  updatedByEmail: string,
) {
  if (!isAppPlatform(input.platform)) throw new Error("invalid-platform");
  if (!VERSION_PATTERN.test(input.latestVersion) || !VERSION_PATTERN.test(input.minimumVersion)) {
    throw new Error("invalid-version");
  }
  if (
    !Number.isSafeInteger(input.latestBuild) || input.latestBuild < 0 ||
    !Number.isSafeInteger(input.minimumBuild) || input.minimumBuild < 0
  ) {
    throw new Error("invalid-build");
  }
  if (input.updateMode !== "recommended" && input.updateMode !== "required") {
    throw new Error("invalid-update-mode");
  }
  if (compareAppBuild(input.minimumVersion, input.minimumBuild, input.latestVersion, input.latestBuild) > 0) {
    throw new Error("minimum-above-latest");
  }
  if (input.releaseNotes.length > 1000) throw new Error("release-notes-too-long");
  await ensureOfficialMailSchema();
  await getDbPool().query(
    `INSERT INTO app_version_policies
       (platform, latest_version, latest_build, minimum_version, minimum_build,
        update_mode, release_notes, updated_by_email)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?)
     ON DUPLICATE KEY UPDATE
       latest_version = VALUES(latest_version), latest_build = VALUES(latest_build),
       minimum_version = VALUES(minimum_version), minimum_build = VALUES(minimum_build),
       update_mode = VALUES(update_mode), release_notes = VALUES(release_notes),
       updated_by_email = VALUES(updated_by_email), updated_at = NOW(3)`,
    [
      input.platform, input.latestVersion, input.latestBuild,
      input.minimumVersion, input.minimumBuild, input.updateMode,
      input.releaseNotes.trim(), updatedByEmail,
    ],
  );
  return getAppVersionPolicy(input.platform);
}

export async function isLegacyNativeAuthEnabled() {
  await ensureOfficialMailSchema();
  const [rows] = await getDbPool().query<(RowDataPacket & { legacy_native_auth_enabled: number })[]>(
    "SELECT legacy_native_auth_enabled FROM app_version_settings WHERE id = 1 LIMIT 1",
  );
  return rows[0]?.legacy_native_auth_enabled !== 0;
}

export async function setLegacyNativeAuthEnabled(enabled: boolean, updatedByEmail: string) {
  await ensureOfficialMailSchema();
  await getDbPool().query(
    `INSERT INTO app_version_settings (id, legacy_native_auth_enabled, updated_by_email)
     VALUES (1, ?, ?)
     ON DUPLICATE KEY UPDATE legacy_native_auth_enabled = VALUES(legacy_native_auth_enabled),
       updated_by_email = VALUES(updated_by_email), updated_at = NOW(3)`,
    [enabled ? 1 : 0, updatedByEmail],
  );
}
