import assert from "node:assert/strict";
import test from "node:test";

import { loadVerifierConfig } from "./config.js";
import { createProductionRoute, createServer } from "./server.js";
import { createVerifyPurchaseRoute } from "./verify-purchase.js";

const VALID_ENV = {
  PORT: "8080",
  GOOGLE_PLAY_PACKAGE_NAME: "com.makecdev.coderemotemanager",
  GOOGLE_PLAY_PRODUCT_ID: "pro_multi_account",
  PUBLIC_BASE_URL: "https://entitlements.example.com",
  ENTITLEMENT_SIGNING_KEY_VERSION: "release-2026-01",
  ENTITLEMENT_SIGNING_KEY_FILE: "/var/run/secrets/entitlement/signing-key.pem",
  PUBSUB_OIDC_AUDIENCE: "https://entitlements.example.com/internal/google-play/rtdn",
  PUBSUB_OIDC_SERVICE_ACCOUNTS: "pubsub@example.iam.gserviceaccount.com",
  SCHEDULER_OIDC_AUDIENCE: "https://entitlements.example.com/internal/google-play/voided-purchases/sync",
  SCHEDULER_OIDC_SERVICE_ACCOUNTS: "scheduler@example.iam.gserviceaccount.com",
};

test("loadVerifierConfig accepts only the fixed release identity and safe OIDC allowlists", () => {
  assert.deepEqual(loadVerifierConfig(VALID_ENV), {
    port: 8080,
    packageName: "com.makecdev.coderemotemanager",
    productId: "pro_multi_account",
    publicBaseUrl: "https://entitlements.example.com",
    signingKeyVersion: "release-2026-01",
    signingKeyFile: "/var/run/secrets/entitlement/signing-key.pem",
    requestTimeoutMs: 10_000,
    pubsubOidc: {
      audience: "https://entitlements.example.com/internal/google-play/rtdn",
      serviceAccounts: ["pubsub@example.iam.gserviceaccount.com"],
    },
    schedulerOidc: {
      audience: "https://entitlements.example.com/internal/google-play/voided-purchases/sync",
      serviceAccounts: ["scheduler@example.iam.gserviceaccount.com"],
    },
  });

  const invalid = [
    { PORT: "0" },
    { GOOGLE_PLAY_PACKAGE_NAME: "another.package" },
    { GOOGLE_PLAY_PRODUCT_ID: "another_product" },
    { PUBLIC_BASE_URL: "http://entitlements.example.com" },
    { ENTITLEMENT_SIGNING_KEY_VERSION: "../../secret" },
    { ENTITLEMENT_SIGNING_KEY_FILE: "relative/signing-key.pem" },
    { GOOGLE_APPLICATION_CREDENTIALS: "/var/run/secrets/service-account.json" },
    { PUBSUB_OIDC_AUDIENCE: "http://entitlements.example.com/rtdn" },
    { PUBSUB_OIDC_AUDIENCE: "https://entitlements.example.com/wrong-rtdn" },
    { PUBSUB_OIDC_SERVICE_ACCOUNTS: "not-an-email" },
    { SCHEDULER_OIDC_AUDIENCE: "https://other.example.com/internal/google-play/voided-purchases/sync" },
    { SCHEDULER_OIDC_SERVICE_ACCOUNTS: "" },
  ];
  for (const override of invalid) {
    assert.throws(
      () => loadVerifierConfig({ ...VALID_ENV, ...override }),
      (error) => error?.code === "invalid_verifier_config" && error?.message === "Verifier configuration is invalid."
    );
  }
});

test("HTTP shell serves only a minimal health response and safe route errors", async (t) => {
  const context = await listen(t);

  const health = await fetch(`${context.baseUrl}/healthz`);
  assert.equal(health.status, 200);
  assert.deepEqual(await health.json(), { ok: true });
  assert.equal(health.headers.get("cache-control"), "no-store");

  const method = await fetch(`${context.baseUrl}/healthz`, { method: "POST" });
  assert.equal(method.status, 405);
  assert.deepEqual(await method.json(), { error: "method_not_allowed" });

  const missing = await fetch(`${context.baseUrl}/missing`);
  assert.equal(missing.status, 404);
  assert.deepEqual(await missing.json(), { error: "not_found" });
});

test("JSON reader rejects unsupported, malformed, and oversized request bodies", async (t) => {
  const context = await listen(t, {
    resolveRoute: async ({ req, url, readJson }) => {
      if (req.method !== "POST" || url.pathname !== "/echo") return null;
      return { status: 200, body: await readJson(req) };
    },
  });

  const unsupported = await fetch(`${context.baseUrl}/echo`, {
    method: "POST",
    headers: { "content-type": "text/plain" },
    body: "private purchase token",
  });
  assert.equal(unsupported.status, 415);
  assert.deepEqual(await unsupported.json(), { error: "unsupported_media_type" });

  const malformed = await fetch(`${context.baseUrl}/echo`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: "{bad",
  });
  assert.equal(malformed.status, 400);
  assert.deepEqual(await malformed.json(), { error: "invalid_json" });

  const oversized = await fetch(`${context.baseUrl}/echo`, {
    method: "POST",
    headers: { "content-type": "application/json; charset=utf-8" },
    body: JSON.stringify({ value: "x".repeat(17 * 1024) }),
  });
  assert.equal(oversized.status, 413);
  assert.deepEqual(await oversized.json(), { error: "request_too_large" });
});

test("request timeout returns a fixed envelope without waiting for the route", async (t) => {
  const context = await listen(t, {
    config: { requestTimeoutMs: 20 },
    resolveRoute: async ({ url }) => {
      if (url.pathname === "/slow") await new Promise(() => {});
      return null;
    },
  });

  const response = await fetch(`${context.baseUrl}/slow`);
  assert.equal(response.status, 504);
  assert.deepEqual(await response.json(), { error: "request_timeout" });
});

test("structured request logs never contain request bodies, credentials, paths, or raw errors", async (t) => {
  const lines = [];
  const logger = {
    info(line) { lines.push(line); },
    warn(line) { lines.push(line); },
    error(line) { lines.push(line); },
  };
  const context = await listen(t, {
    logger,
    resolveRoute: async ({ url }) => {
      if (url.pathname !== "/boom") return null;
      const error = new Error("Bearer secret-purchase-token at C:\\private\\key.json");
      error.status = 500;
      error.code = "google_raw_failure";
      throw error;
    },
  });

  const response = await fetch(`${context.baseUrl}/boom`, {
    headers: { authorization: "Bearer browser-secret" },
  });
  assert.equal(response.status, 500);
  assert.deepEqual(await response.json(), { error: "internal_error" });
  assert.equal(lines.length, 1);
  assert.deepEqual(JSON.parse(lines[0]), {
    level: "error",
    code: "request_failed",
    status: 500,
    errorCode: "internal_error",
  });
  const serialized = lines.join("\n");
  for (const secret of ["secret-purchase-token", "browser-secret", "C:\\private", "google_raw_failure"]) {
    assert.equal(serialized.includes(secret), false);
  }
});

test("server passes a client identity to the public verification route", async (t) => {
  let limitedIp;
  const resolveRoute = createVerifyPurchaseRoute({
    googlePlay: {
      async verifyPurchase() {
        return { state: "purchased", acknowledged: true, purchaseCompletionTime: "2026-08-02T11:55:00Z" };
      },
    },
    purchaseStore: { async recordActive() { return { state: "active" }; } },
    signer: { sign() { return { certificate: "header.claims.signature", expiresAt: "2026-09-01T12:00:00.000Z" }; } },
    rateLimiter: {
      consume({ clientIp }) {
        limitedIp = clientIp;
        return { allowed: true };
      },
    },
    packageName: "com.makecdev.coderemotemanager",
    productId: "pro_multi_account",
  });
  const context = await listen(t, { resolveRoute });
  const response = await fetch(`${context.baseUrl}/v1/entitlements/google-play/verify`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      purchaseToken: "purchase-token-1234567890",
      backendKeyThumbprint: "a".repeat(43),
      challengeHash: "b".repeat(43),
    }),
  });

  assert.equal(response.status, 200);
  assert.deepEqual(await response.json(), {
    certificate: "header.claims.signature",
    expiresAt: "2026-09-01T12:00:00.000Z",
  });
  assert.match(limitedIp, /127\.0\.0\.1/);
});

test("production route reads only the mounted signing value and serves verification", async (t) => {
  const { generateKeyPairSync } = await import("node:crypto");
  const privateKey = generateKeyPairSync("ed25519").privateKey.export({ format: "pem", type: "pkcs8" });
  const config = loadVerifierConfig(VALID_ENV);
  let readPath;
  const resolveRoute = await createProductionRoute(config, {
    async readFile(path) { readPath = path; return privateKey; },
    firestore: memoryFirestore(),
    googlePlay: {
      async verifyPurchase() {
        return { state: "purchased", acknowledged: true, purchaseCompletionTime: "2026-08-02T11:55:00Z" };
      },
    },
    rateLimiter: { consume() { return { allowed: true }; } },
  });
  const context = await listen(t, { resolveRoute });
  const response = await fetch(`${context.baseUrl}/v1/entitlements/google-play/verify`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      purchaseToken: "purchase-token-1234567890",
      backendKeyThumbprint: "a".repeat(43),
      challengeHash: "b".repeat(43),
    }),
  });

  assert.equal(response.status, 200);
  assert.match((await response.json()).certificate, /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/);
  assert.equal(readPath, "/var/run/secrets/entitlement/signing-key.pem");
});

test("production resolver wires authenticated RTDN and voided-purchase synchronization", async () => {
  const { generateKeyPairSync } = await import("node:crypto");
  const privateKey = generateKeyPairSync("ed25519").privateKey.export({ format: "pem", type: "pkcs8" });
  const config = loadVerifierConfig(VALID_ENV);
  const calls = [];
  const purchaseStore = {
    async recordActive() { return { state: "active" }; },
    async invalidate(input) { calls.push(["invalidate", input.purchaseToken, input.reason]); },
  };
  const resolveRoute = await createProductionRoute(config, {
    async readFile() { return privateKey; },
    googlePlay: { async verifyPurchase() { throw new Error("not used"); } },
    purchaseStore,
    oidc: { async verifyRequest(_req, oidcConfig) { calls.push(["auth", oidcConfig.audience]); } },
    messageStore: { async record(id) { calls.push(["receipt", id]); return { duplicate: false }; } },
    voidedPurchases: { async list() { calls.push(["list"]); return { purchaseTokens: ["sync-purchase-token-1234"], nextCursor: null }; } },
  });

  const rtdn = await resolveRoute({
    req: { method: "POST", headers: { authorization: "Bearer signed" } },
    url: new URL(VALID_ENV.PUBSUB_OIDC_AUDIENCE),
    readJson: async () => ({
      message: {
        messageId: "event-1",
        data: Buffer.from(JSON.stringify({
          version: "1.0",
          packageName: config.packageName,
          eventTimeMillis: "1785672000000",
          oneTimeProductNotification: {
            version: "1.0",
            notificationType: 2,
            purchaseToken: "rtdn-purchase-token-1234",
            sku: config.productId,
          },
        })).toString("base64"),
      },
      subscription: "projects/example/subscriptions/play-events",
    }),
  });
  const sync = await resolveRoute({
    req: { method: "POST", headers: { authorization: "Bearer signed" } },
    url: new URL(VALID_ENV.SCHEDULER_OIDC_AUDIENCE),
    readJson: async () => ({}),
  });

  assert.deepEqual(rtdn, { status: 204, body: null });
  assert.deepEqual(sync, { status: 200, body: { processed: 1, nextCursor: null } });
  assert.deepEqual(calls, [
    ["auth", VALID_ENV.PUBSUB_OIDC_AUDIENCE],
    ["invalidate", "rtdn-purchase-token-1234", "canceled"],
    ["receipt", "event-1"],
    ["auth", VALID_ENV.SCHEDULER_OIDC_AUDIENCE],
    ["list"],
    ["invalidate", "sync-purchase-token-1234", "voided"],
  ]);
});

async function listen(t, dependencies = {}) {
  const config = {
    ...loadVerifierConfig(VALID_ENV),
    ...dependencies.config,
  };
  const logger = dependencies.logger ?? { info() {}, warn() {}, error() {} };
  const server = createServer({ config, ...dependencies, logger });
  await new Promise((resolve, reject) => {
    server.once("error", reject);
    server.listen(0, "127.0.0.1", resolve);
  });
  t.after(() => new Promise((resolve) => server.close(resolve)));
  const address = server.address();
  return { baseUrl: `http://127.0.0.1:${address.port}` };
}

function memoryFirestore() {
  const docs = new Map();
  return {
    collection(name) { return { doc(id) { return { path: `${name}/${id}` }; } }; },
    async runTransaction(operation) {
      const writes = [];
      const result = await operation({
        async get(reference) {
          const value = docs.get(reference.path);
          return { exists: value !== undefined, data: () => structuredClone(value) };
        },
        set(reference, value) { writes.push([reference.path, structuredClone(value)]); },
      });
      for (const [path, value] of writes) docs.set(path, value);
      return result;
    },
  };
}
