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

import { publicErrorResponse } from "./http.js";
import { createVoidedPurchasesClient, createVoidedPurchasesRoute } from "./voided-purchases.js";

const PACKAGE_NAME = "com.makecdev.coderemotemanager";
const PRODUCT_ID = "pro_multi_account";
const TOKEN = "purchase-token-1234567890";
const CONFIG = {
  packageName: PACKAGE_NAME,
  productId: PRODUCT_ID,
  schedulerOidc: {
    audience: "https://verifier.example.com/internal/google-play/voided-purchases/sync",
    serviceAccounts: ["scheduler@example-project.iam.gserviceaccount.com"],
  },
};

test("voided purchase client uses ADC, one-time-only filters, and cursor pagination", async () => {
  let requested;
  const client = createVoidedPurchasesClient({
    auth: { async getRequestHeaders(url) { assert.match(url, /^https:\/\/androidpublisher\.googleapis\.com\//); return { authorization: "Bearer adc" }; } },
    fetch: async (url, options) => {
      requested = { url, options };
      return response({
        voidedPurchases: [{ purchaseToken: TOKEN, voidedQuantity: 1 }],
        tokenPagination: { nextPageToken: "next-page" },
      });
    },
    packageName: PACKAGE_NAME,
  });

  assert.deepEqual(await client.list({ cursor: "current-page" }), {
    purchaseTokens: [TOKEN],
    nextCursor: "next-page",
  });
  const url = new URL(requested.url);
  assert.equal(url.pathname, `/androidpublisher/v3/applications/${PACKAGE_NAME}/purchases/voidedpurchases`);
  assert.deepEqual([...url.searchParams].sort(), [
    ["includeQuantityBasedPartialRefund", "true"],
    ["pageSelection.maxResults", "1000"],
    ["pageSelection.token", "current-page"],
    ["type", "0"],
  ]);
  assert.equal(requested.options.method, "GET");
  assert.equal(new Headers(requested.options.headers).get("authorization"), "Bearer adc");
});

test("voided sync authenticates before body read and invalidates every returned purchase", async () => {
  const calls = [];
  const route = createVoidedPurchasesRoute({
    oidc: { async verifyRequest(_req, config) { calls.push("auth"); assert.equal(config, CONFIG.schedulerOidc); } },
    client: { async list(input) { calls.push("list"); assert.deepEqual(input, { cursor: "page-1" }); return { purchaseTokens: [TOKEN, `${TOKEN}-2`], nextCursor: "page-2" }; } },
    purchaseStore: { async invalidate(input) { calls.push(input.purchaseToken); assert.equal(input.reason, "voided"); } },
    config: CONFIG,
  });
  const result = await routeInput(route, { cursor: "page-1" }, () => calls.push("read"));

  assert.deepEqual(result, { status: 200, body: { processed: 2, nextCursor: "page-2" } });
  assert.deepEqual(calls, ["auth", "read", "list", TOKEN, `${TOKEN}-2`]);
});

test("failed scheduler authentication does not read the request body", async () => {
  let reads = 0;
  const route = createVoidedPurchasesRoute({
    oidc: { async verifyRequest() { throw Object.assign(new Error(), { status: 401, code: "unauthorized" }); } },
    client: { async list() { throw new Error("must not run"); } },
    purchaseStore: { async invalidate() { throw new Error("must not run"); } },
    config: CONFIG,
  });
  await assert.rejects(routeInput(route, {}, () => { reads += 1; }), (error) => error.code === "unauthorized");
  assert.equal(reads, 0);
});

test("voided sync validates cursors and reduces upstream details to a fixed failure", async () => {
  const invalidRoute = basicRoute({ async list() { throw new Error("must not run"); } });
  await assert.rejects(routeInput(invalidRoute, { cursor: "x".repeat(2049) }), (error) => {
    assert.deepEqual(publicErrorResponse(error), { status: 400, code: "invalid_voided_sync_request" });
    return true;
  });

  const failingRoute = basicRoute({ async list() { throw new Error(`Google rejected ${TOKEN}`); } });
  await assert.rejects(routeInput(failingRoute, {}), (error) => {
    assert.deepEqual(publicErrorResponse(error), { status: 503, code: "voided_sync_unavailable" });
    assert.equal(error.message.includes(TOKEN), false);
    return true;
  });
});

test("voided client rejects malformed and excessive Google responses without exposing tokens", async (t) => {
  const cases = [
    { voidedPurchases: [{ purchaseToken: "bad token" }] },
    { voidedPurchases: Array.from({ length: 1001 }, () => ({ purchaseToken: TOKEN })) },
    { voidedPurchases: [], tokenPagination: { nextPageToken: "x".repeat(2049) } },
  ];
  for (const body of cases) {
    await t.test("malformed response", async () => {
      const client = createVoidedPurchasesClient({
        auth: { async getRequestHeaders() { return {}; } },
        fetch: async () => response(body),
        packageName: PACKAGE_NAME,
      });
      await assert.rejects(client.list({}), (error) => {
        assert.equal(error.code, "voided_purchases_unavailable");
        assert.equal(error.message.includes(TOKEN), false);
        return true;
      });
    });
  }
});

function basicRoute(client) {
  return createVoidedPurchasesRoute({
    oidc: { async verifyRequest() {} },
    client,
    purchaseStore: { async invalidate() {} },
    config: CONFIG,
  });
}

async function routeInput(route, body, onRead = () => {}) {
  return route({
    req: { method: "POST", headers: { authorization: "Bearer token" } },
    url: new URL("https://verifier.example.com/internal/google-play/voided-purchases/sync"),
    readJson: async (_req, maxBytes) => { onRead(); assert.equal(maxBytes, 4 * 1024); return body; },
  });
}

function response(body, status = 200) {
  return { ok: status >= 200 && status < 300, status, async json() { return structuredClone(body); } };
}
