import { describe, expect, test } from "bun:test";
import { createContext, type Context } from "@wrnexus/core";
import { captchaGuard, captchaPageGate } from "../src/middleware.ts";
import type { CaptchaEngine } from "../src/types.ts";

function fakeEngine(): CaptchaEngine {
  return {
    provider: "self-hosted",
    basePath: "/api/captcha",
    async create() {
      throw new Error("not used");
    },
    async verify() {
      throw new Error("not used");
    },
    async verifyResponseToken(input) {
      return input.responseToken === "verified-token"
        ? { success: true, provider: "self-hosted", action: input.action }
        : { success: false, provider: "self-hosted", action: input.action, code: "invalid-input" };
    },
    async renderAudio() {
      return undefined;
    },
    async gc() {},
  };
}

function sessionFixture() {
  const values = new Map<string, unknown>();
  let sessionId = "session-1";

  return {
    id(): string {
      return sessionId;
    },
    get<T>(key: string): T | undefined {
      return values.get(key) as T | undefined;
    },
    getAll(): Record<string, unknown> {
      return Object.fromEntries(values);
    },
    set(key: string, value: unknown): void {
      values.set(key, value);
    },
    delete(key: string): void {
      values.delete(key);
    },
    regenerate(): void {
      sessionId = `${sessionId}-regenerated`;
    },
    clear(): void {
      values.clear();
    },
  };
}

describe("CAPTCHA request guard", () => {
  test("reuses an action-bound verified session grant for retryable requests", async () => {
    const session = sessionFixture();
    const guard = captchaGuard({
      action: "auth-login",
      engine: fakeEngine(),
      verifiedForMs: 5 * 60_000,
    });
    const firstRequest = new Request("https://example.test/api/auth/login", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ "wrn-captcha-response": "verified-token" }),
    });
    const firstContext: Context = {
      ...createContext(firstRequest, new URL(firstRequest.url)),
      session,
      ip: "127.0.0.1",
    };
    const first = await guard(
      firstContext,
      () => new Response("credentials-invalid", { status: 401 }),
    );
    expect(first.status).toBe(401);

    const retryRequest = new Request("https://example.test/api/auth/login", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({}),
    });
    const retryContext: Context = {
      ...createContext(retryRequest, new URL(retryRequest.url)),
      session,
      ip: "127.0.0.1",
    };
    const retry = await guard(retryContext, () => new Response("retry-allowed"));

    expect(await retry.text()).toBe("retry-allowed");
    expect(retryContext.locals.captcha).toMatchObject({
      success: true,
      action: "auth-login",
    });
    expect(retryContext.locals.captchaVerified).toBe(true);
  });

  test("rejects invalid verified-session durations", () => {
    expect(() =>
      captchaGuard({
        action: "auth-login",
        engine: fakeEngine(),
        verifiedForMs: Number.NaN,
      }),
    ).toThrow("verifiedForMs");
  });
});

describe("CAPTCHA page gate", () => {
  test("accepts a verified form token and grants the protected route", async () => {
    const session = sessionFixture();
    const gate = captchaPageGate({
      action: "protected-page-access",
      engine: fakeEngine(),
      challengePath: "/captcha",
      policy: {
        mode: "session",
        verifiedForMs: 15 * 60_000,
        routeGroups: ["/protected"],
      },
    });

    const body = new URLSearchParams({
      returnTo: "/protected",
      "wrn-captcha-response": "verified-token",
    });
    const request = new Request("https://example.test/api/page-grant", {
      method: "POST",
      headers: { "content-type": "application/x-www-form-urlencoded" },
      body,
    });
    const requestUrl = new URL(request.url);
    const context: Context = {
      ...createContext(request, requestUrl),
      session,
      ip: "127.0.0.1",
    };

    const granted = await gate(context, () =>
      Response.redirect("https://example.test/protected", 303),
    );
    expect(granted.status).toBe(303);

    const protectedRequest = new Request("https://example.test/protected");
    const protectedUrl = new URL(protectedRequest.url);
    const protectedContext: Context = {
      ...createContext(protectedRequest, protectedUrl),
      session,
      ip: "127.0.0.1",
    };
    const allowed = await gate(protectedContext, () => new Response("unlocked"));
    expect(await allowed.text()).toBe("unlocked");
  });
});
