import { createHmac, timingSafeEqual } from "node:crypto";
import { readFileSync } from "node:fs";

import { Type, type Static } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";

export const browserSupportCookieName = "dada_browser_support";
export const browserSupportCookieMaxAgeSeconds = 24 * 60 * 60;

export const BrowserUnsupportedReasonSchema = Type.Union(
  [
    Type.Literal("platform_unsupported"),
    Type.Literal("brand_unsupported"),
    Type.Literal("version_unsupported"),
    Type.Literal("identity_unavailable"),
  ],
  { $id: "BrowserUnsupportedReason" },
);
const BrowserBrandVersionSchema = Type.Object(
  {
    brand: Type.String({ maxLength: 80 }),
    version: Type.String({ maxLength: 80 }),
  },
  { additionalProperties: false },
);
const SupportedBrowserSummarySchema = Type.Object(
  {
    brand: Type.Union([Type.Literal("Google Chrome"), Type.Literal("Microsoft Edge")]),
    major: Type.Integer({ minimum: 1 }),
  },
  { additionalProperties: false },
);
export const BrowserSupportRequestSchema = Type.Object(
  {
    brands: Type.Array(BrowserBrandVersionSchema, { maxItems: 16 }),
    full_version_list: Type.Array(BrowserBrandVersionSchema, { maxItems: 16 }),
    platform: Type.String({ maxLength: 40 }),
  },
  { additionalProperties: false, $id: "BrowserSupportRequest" },
);
export const BrowserSupportSuccessSchema = Type.Object(
  {
    app_version: Type.String({ maxLength: 80 }),
    browser: SupportedBrowserSummarySchema,
    status: Type.Literal("supported"),
    supported_browsers: Type.Array(SupportedBrowserSummarySchema, { maxItems: 2 }),
  },
  { additionalProperties: false, $id: "BrowserSupportSuccess" },
);

export type BrowserUnsupportedReason = Static<typeof BrowserUnsupportedReasonSchema>;
export type BrowserSupportRequest = Static<typeof BrowserSupportRequestSchema>;
type SupportedBrand = "Google Chrome" | "Microsoft Edge";

export interface BrowserSupportRelease {
  appVersion: string;
  browsers: ReadonlyArray<{
    brand: SupportedBrand;
    fullVersion: string;
  }>;
}

interface BrowserIdentity {
  brand: SupportedBrand;
  major: number;
}

type BrowserSupportResult =
  | { identity: BrowserIdentity; supported: true }
  | { reason: BrowserUnsupportedReason; supported: false };

const supportedBrands = new Set<SupportedBrand>(["Google Chrome", "Microsoft Edge"]);
const fullVersionPattern = /^[1-9][0-9]*\.[0-9]+\.[0-9]+\.[0-9]+$/;

function major(version: string) {
  if (!/^[1-9][0-9]*(?:\.[0-9]+)*$/.test(version)) return undefined;
  const first = version.split(".")[0];
  if (!first) return undefined;
  const value = Number.parseInt(first, 10);
  return Number.isSafeInteger(value) ? value : undefined;
}

function parsedHeaderList(value: string | undefined) {
  if (!value) return undefined;
  const entries: Array<{ brand: string; version: string }> = [];
  const remainder = value.replace(/"([^"]+)"\s*;\s*v="([^"]+)"/g, (_match, brand, version) => {
    entries.push({ brand, version });
    return "";
  });
  if (entries.length === 0 || !/^[\s,]*$/.test(remainder)) return undefined;
  return entries;
}

function parsedPlatform(value: string | undefined) {
  const match = value?.match(/^"([^"]+)"$/);
  return match?.[1];
}

function normalizedList(entries: Array<{ brand: string; version: string }>) {
  return [...entries]
    .sort((left, right) => left.brand.localeCompare(right.brand) || left.version.localeCompare(right.version))
    .map(({ brand, version }) => `${brand}\u0000${version}`)
    .join("\u0001");
}

function supportedIdentity(entries: Array<{ brand: string; version: string }>) {
  const matches = entries.filter(({ brand }) => supportedBrands.has(brand as SupportedBrand));
  if (matches.length !== 1) return undefined;
  const entry = matches[0];
  if (!entry) return undefined;
  const parsedMajor = major(entry.version);
  if (!parsedMajor) return undefined;
  return { brand: entry.brand as SupportedBrand, major: parsedMajor };
}

export function supportedBrowserSummary(release: BrowserSupportRelease | undefined) {
  if (!release) return [];
  return release.browsers.map(({ brand, fullVersion }) => ({ brand, major: major(fullVersion)! }));
}

export function validateBrowserSupportRelease(value: unknown): value is BrowserSupportRelease {
  if (!value || typeof value !== "object") return false;
  const release = value as BrowserSupportRelease;
  if (typeof release.appVersion !== "string" || release.appVersion.length === 0 || release.appVersion.length > 80) {
    return false;
  }
  if (!Array.isArray(release.browsers) || release.browsers.length !== 2) return false;
  const brands = new Set(release.browsers.map(({ brand }) => brand));
  return (
    brands.size === 2 &&
    brands.has("Google Chrome") &&
    brands.has("Microsoft Edge") &&
    release.browsers.every(
      ({ brand, fullVersion }) => supportedBrands.has(brand) && fullVersionPattern.test(fullVersion),
    )
  );
}

export function readBrowserSupportRelease(path: string) {
  try {
    const value = JSON.parse(readFileSync(path, "utf8")) as unknown;
    return validateBrowserSupportRelease(value) ? value : undefined;
  } catch {
    return undefined;
  }
}

export function checkBrowserSupport(
  body: unknown,
  headers: {
    secChUa: string | undefined;
    secChUaFullVersionList: string | undefined;
    secChUaPlatform: string | undefined;
  },
  release: BrowserSupportRelease | undefined,
): BrowserSupportResult {
  if (!Value.Check(BrowserSupportRequestSchema, body)) {
    return { reason: "identity_unavailable", supported: false };
  }
  const request = body as BrowserSupportRequest;
  const headerBrands = parsedHeaderList(headers.secChUa);
  const headerFullVersions = parsedHeaderList(headers.secChUaFullVersionList);
  const headerPlatform = parsedPlatform(headers.secChUaPlatform);
  if (!headerBrands || !headerFullVersions || !headerPlatform) {
    return { reason: "identity_unavailable", supported: false };
  }
  if (
    normalizedList(headerBrands) !== normalizedList(request.brands) ||
    normalizedList(headerFullVersions) !== normalizedList(request.full_version_list) ||
    headerPlatform !== request.platform
  ) {
    return { reason: "identity_unavailable", supported: false };
  }
  if (request.platform !== "Windows") return { reason: "platform_unsupported", supported: false };

  const lowIdentity = supportedIdentity(request.brands);
  const fullIdentity = supportedIdentity(request.full_version_list);
  if (!lowIdentity && request.brands.every(({ brand }) => !supportedBrands.has(brand as SupportedBrand))) {
    return { reason: "brand_unsupported", supported: false };
  }
  if (
    !lowIdentity ||
    !fullIdentity ||
    lowIdentity.brand !== fullIdentity.brand ||
    lowIdentity.major !== fullIdentity.major
  ) {
    return { reason: "identity_unavailable", supported: false };
  }

  const supported = release?.browsers.find(({ brand }) => brand === fullIdentity.brand);
  if (!supported || major(supported.fullVersion) !== fullIdentity.major) {
    return { reason: "version_unsupported", supported: false };
  }
  return { identity: fullIdentity, supported: true };
}

function signature(secret: Buffer, encodedPayload: string) {
  return createHmac("sha256", secret).update(encodedPayload).digest("base64url");
}

export function createBrowserSupportCookie(
  secret: Buffer,
  release: BrowserSupportRelease,
  identity: BrowserIdentity,
  issuedAtSeconds = Math.floor(Date.now() / 1000),
) {
  const encodedPayload = Buffer.from(
    JSON.stringify({
      app_version: release.appVersion,
      brand: identity.brand,
      issued_at: issuedAtSeconds,
      major: identity.major,
    }),
  ).toString("base64url");
  return `${encodedPayload}.${signature(secret, encodedPayload)}`;
}

function cookieValue(cookieHeader: string | undefined) {
  for (const pair of cookieHeader?.split(";") ?? []) {
    const [name, ...rest] = pair.trim().split("=");
    if (name === browserSupportCookieName) return rest.join("=");
  }
  return undefined;
}

export function verifyBrowserSupportCookie(input: {
  cookieHeader: string | undefined;
  nowSeconds?: number;
  release: BrowserSupportRelease | undefined;
  secChUa: string | undefined;
  secret: Buffer;
}) {
  if (!input.release) return { reason: "version_unsupported" as const, supported: false as const };
  const value = cookieValue(input.cookieHeader);
  if (!value) return { reason: "identity_unavailable" as const, supported: false as const };
  const [encodedPayload, encodedSignature, extra] = value.split(".");
  if (!encodedPayload || !encodedSignature || extra) {
    return { reason: "identity_unavailable" as const, supported: false as const };
  }
  const expected = Buffer.from(signature(input.secret, encodedPayload));
  const actual = Buffer.from(encodedSignature);
  if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) {
    return { reason: "identity_unavailable" as const, supported: false as const };
  }

  let payload: { app_version?: unknown; brand?: unknown; issued_at?: unknown; major?: unknown };
  try {
    payload = JSON.parse(Buffer.from(encodedPayload, "base64url").toString("utf8"));
  } catch {
    return { reason: "identity_unavailable" as const, supported: false as const };
  }
  const now = input.nowSeconds ?? Math.floor(Date.now() / 1000);
  if (
    payload.app_version !== input.release.appVersion ||
    !supportedBrands.has(payload.brand as SupportedBrand) ||
    !Number.isSafeInteger(payload.major) ||
    !Number.isSafeInteger(payload.issued_at) ||
    (payload.issued_at as number) > now ||
    now - (payload.issued_at as number) > browserSupportCookieMaxAgeSeconds
  ) {
    return { reason: "identity_unavailable" as const, supported: false as const };
  }

  const headerBrands = parsedHeaderList(input.secChUa);
  const currentIdentity = headerBrands ? supportedIdentity(headerBrands) : undefined;
  if (!currentIdentity) return { reason: "identity_unavailable" as const, supported: false as const };
  if (currentIdentity.brand !== payload.brand) {
    return { reason: "identity_unavailable" as const, supported: false as const };
  }
  const supported = input.release.browsers.find(({ brand }) => brand === currentIdentity.brand);
  if (currentIdentity.major !== payload.major || major(supported?.fullVersion ?? "") !== currentIdentity.major) {
    return { reason: "version_unsupported" as const, supported: false as const };
  }
  return { identity: currentIdentity, supported: true as const };
}
