/* global FormData, Headers, console */

import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
  createKeyring,
  createMemoryReplayStore,
  encryptHttpBody,
  decryptHttpBody,
  createEncryptedRequest,
  encryptResponse,
  decryptEncryptedResponse,
} from "../packages/encryption/src/index.ts";
import {
  createAccessToken,
  createRefreshToken,
  verifyAccessToken,
  verifyRefreshToken,
  hasScopes,
  jwtCookie,
  createTokenPair,
  readJwtCookie,
} from "../packages/jwt/src/index.ts";
import { createDb, createRepository, batch, databaseHealth } from "../packages/db/src/index.ts";
import {
  loadLocales,
  resolveI18n,
  makeT,
  resolveLang,
  renderI18nData,
} from "../packages/i18n/src/index.ts";
import {
  createPicture,
  createBlurPlaceholder,
  imageCacheKey,
} from "../packages/image/src/index.ts";
import {
  createRealtimeMessage,
  parseRealtimeMessage,
  roomMemberSummary,
} from "../packages/realtime/src/index.ts";
import { captchaFields, captchaHeaders, captchaTokenFrom } from "../packages/captcha/src/index.ts";
import { uploaderAttributes, formatFileSize } from "../packages/uploader/src/index.ts";
import { v, parseOrThrow, validationSummary } from "../packages/validation/src/index.ts";
import { authRoute, authFailure } from "../packages/auth/src/index.ts";

const keyring = createKeyring([
  { id: "active", secret: "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=", active: true },
]);
const replayStore = createMemoryReplayStore(() => 1_000);
const envelope = await encryptHttpBody(
  { value: 42 },
  {
    keyring,
    method: "POST",
    url: "https://example.test/api/items",
    requestId: "request-1",
    timestamp: 1_000,
  },
);
const decrypted = await decryptHttpBody(envelope, {
  keyring,
  method: "POST",
  url: "https://example.test/api/items",
  replayStore,
  now: () => 1_000,
});
assert.deepEqual(decrypted.body, { value: 42 });
await assert.rejects(
  () =>
    decryptHttpBody(envelope, {
      keyring,
      method: "POST",
      url: "https://example.test/api/items",
      replayStore,
      now: () => 1_000,
    }),
  /REPLAY/,
);
await assert.rejects(
  () =>
    decryptHttpBody(envelope, {
      keyring,
      method: "GET",
      url: "https://example.test/api/items",
      now: () => 1_000,
    }),
  /CONTEXT/,
);
const encryptedRequest = await createEncryptedRequest(
  "https://example.test/api/items",
  { value: 42 },
  { keyring, method: "POST", requestId: "request-2" },
);
const encryptedResponse = await encryptResponse({ ok: true }, encryptedRequest, { keyring });
assert.equal(
  (await decryptEncryptedResponse(encryptedResponse, encryptedRequest, { keyring })).body.ok,
  true,
);

const secret = "a-secure-jwt-secret-for-package-kit-tests";
const access = await createAccessToken("user-1", secret, {
  scopes: ["read", "write"],
  now: 100,
  expiresIn: 60,
});
const refresh = await createRefreshToken("user-1", secret, {
  family: "family-1",
  now: 100,
  expiresIn: 600,
});
const accessClaims = await verifyAccessToken(access, secret, { now: 120 });
const refreshClaims = await verifyRefreshToken(refresh, secret, { now: 120 });
assert.equal(accessClaims.sub, "user-1");
assert.equal(refreshClaims.type, "refresh");
assert.equal(hasScopes(accessClaims, ["read", "write"]), true);
assert.match(jwtCookie(access), /^__Host-wrn_token=/);
assert.equal(readJwtCookie(`__Host-wrn_token=${access}`), access);
const tokenPair = await createTokenPair("user-1", {
  accessSecret: secret,
  accessOptions: { now: 100 },
  refreshOptions: { now: 100 },
});
assert.equal((await verifyAccessToken(tokenPair.accessToken, secret, { now: 120 })).sub, "user-1");

const queries = [];
const driver = {
  dialect: "sqlite",
  async query(sql, params = []) {
    queries.push({ sql, params });
    if (/COUNT/.test(sql)) return [{ count: 2 }];
    if (/SELECT 1 AS healthy/.test(sql)) return [{ healthy: 1 }];
    return [{ id: 1, name: "One" }];
  },
  async exec(sql, params = []) {
    queries.push({ sql, params });
    return { changes: 1, lastInsertId: 1 };
  },
  async transaction(fn) {
    return fn(this);
  },
  close() {},
};
const db = createDb(driver);
const repository = createRepository(db, { table: "items", allowedColumns: ["name"] });
assert.equal((await repository.find(1)).name, "One");
await repository.create({ name: "Two" });
await repository.update(1, { name: "Changed" });
assert.equal(await repository.count(), 2);
assert.deepEqual(batch([1, 2, 3, 4, 5], 2), [[1, 2], [3, 4], [5]]);
assert.equal((await databaseHealth(db)).ok, true);
assert.ok(queries.some((entry) => /INSERT INTO items/.test(entry.sql)));
assert.throws(() => batch([1, 2], Number.NaN), /finite/);

const localeDir = mkdtempSync(join(tmpdir(), "wrn-i18n-"));
try {
  writeFileSync(join(localeDir, "en.json"), JSON.stringify({ common: { hello: "Hello {name}" } }));
  mkdirSync(join(localeDir, "fr"));
  writeFileSync(join(localeDir, "fr", "common.json"), JSON.stringify({ hello: "Bonjour {name}" }));
  const i18n = resolveI18n(loadLocales(localeDir, { strict: true }), {
    default: "en",
    locales: ["en", "fr"],
  });
  assert.equal(makeT(i18n, "fr")("common.hello", { name: "Ajay" }), "Bonjour Ajay");
  assert.equal(resolveLang(i18n, undefined, "fr;q=0.9,en;q=0.8"), "fr");
  assert.equal(resolveLang(i18n, undefined, "*"), "en");
  assert.match(renderI18nData(i18n, "fr"), /Bonjour/);
} finally {
  rmSync(localeDir, { recursive: true, force: true });
}

const picture = createPicture({
  src: "/hero.jpg",
  alt: "Hero",
  width: 1200,
  height: 630,
  widths: [480, 768, 1200],
  formats: ["avif", "webp"],
});
assert.equal(picture.sources.length, 2);
assert.match(createBlurPlaceholder(), /^data:image\/svg\+xml/);
assert.equal(
  imageCacheKey({ src: "/hero.jpg", width: 480 }),
  imageCacheKey({ src: "/hero.jpg", width: 480 }),
);

const message = createRealtimeMessage({
  type: "message",
  room: "support",
  data: { text: "Hello" },
});
assert.equal(
  parseRealtimeMessage(JSON.stringify(message), { room: "support", allowedTypes: ["message"] })
    .room,
  "support",
);
assert.throws(
  () => parseRealtimeMessage(JSON.stringify(message), { room: "other" }),
  /does not match/,
);
assert.deepEqual(
  roomMemberSummary([
    { userId: "1", status: "online" },
    { userId: "2", status: "away" },
  ]),
  { total: 2, online: 1, away: 1, busy: 0 },
);

const form = new FormData();
form.set("wrn-captcha-response", "token");
assert.equal(captchaTokenFrom(form), "token");
assert.deepEqual(captchaFields("token"), { "wrn-captcha-response": "token" });
assert.equal(new Headers(captchaHeaders("token")).get("x-wrn-captcha-token"), "token");

assert.equal(uploaderAttributes({ store: "public", multiple: true })["data-uploader"], "public");
assert.match(formatFileSize(1024), /KB/);

const schema = v.object({ email: v.string().email() });
assert.deepEqual(parseOrThrow(schema, { email: "user@example.com" }), {
  email: "user@example.com",
});
assert.deepEqual(validationSummary({ email: "Invalid" }), [{ field: "email", message: "Invalid" }]);
assert.equal(authRoute("signIn", { basePath: "/auth" }), "/auth/sign-in");
assert.equal((await authFailure("invalid", "Invalid").json()).ok, false);

console.log("Package kit runtime probes passed.");
