import { expect, test } from "bun:test";
import type { Context } from "@wrnexus/core";
import { createAuthEngine } from "../src/engine.ts";
import { createAuthHttpHandlers } from "../src/http/index.ts";
import { MemoryAuthStore } from "../src/stores/memory.ts";

function context(request: Request): Context {
  const values = new Map<string, unknown>();
  return {
    req: request,
    url: new URL(request.url),
    params: {},
    locals: {},
    lang: "en",
    t: (key: string) => key,
    ip: "127.0.0.1",
    user: null,
    cookies: {} as Context["cookies"],
    localStorage: {} as Context["localStorage"],
    session: {
      id: () => "http-test-session",
      get: <T>(key: string) => values.get(key) as T | undefined,
      getAll: () => Object.fromEntries(values),
      set: (key: string, value: unknown) => {
        values.set(key, value);
      },
      delete: (key: string) => {
        values.delete(key);
      },
      regenerate: () => {},
      clear: () => {
        values.clear();
      },
    },
  } as Context;
}

test("login handler never trusts a browser captchaVerified field", async () => {
  const engine = createAuthEngine({
    store: new MemoryAuthStore(),
    secret: "http-handler-secret-that-is-longer-than-thirty-two-characters",
    captchaThreshold: 0,
  });
  await engine.register({ email: "captcha@example.com", password: "StrongPassword123" });
  const handlers = createAuthHttpHandlers({ engine });
  const ctx = context(
    new Request("https://example.test/api/auth/login", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        identifier: "captcha@example.com",
        password: "StrongPassword123",
        captchaVerified: true,
      }),
    }),
  );
  const response = await handlers.login(ctx);
  expect(response.status).toBe(401);
  expect(await response.json()).toMatchObject({ code: "captcha-required" });
});

test("server-populated CAPTCHA verification permits the login attempt", async () => {
  const engine = createAuthEngine({
    store: new MemoryAuthStore(),
    secret: "http-handler-secret-that-is-longer-than-thirty-two-characters",
    captchaThreshold: 0,
  });
  await engine.register({ email: "verified@example.com", password: "StrongPassword123" });
  const handlers = createAuthHttpHandlers({ engine });
  const ctx = context(
    new Request("https://example.test/api/auth/login", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ identifier: "verified@example.com", password: "StrongPassword123" }),
    }),
  );
  ctx.locals.captcha = { success: true };
  const response = await handlers.login(ctx);
  expect(response.status).toBe(200);
  expect(await response.json()).toMatchObject({ ok: true });
});

test("login API returns a safe actionable invalid-credentials message", async () => {
  const engine = createAuthEngine({
    store: new MemoryAuthStore(),
    secret: "http-login-message-secret-longer-than-thirty-two-characters",
  });
  const handlers = createAuthHttpHandlers({ engine });
  const response = await handlers.login(
    context(
      new Request("https://example.test/api/auth/login", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          identifier: "missing@example.com",
          password: "WrongPassword999",
        }),
      }),
    ),
  );

  expect(response.status).toBe(401);
  expect(await response.json()).toMatchObject({
    ok: false,
    code: "invalid-credentials",
    message: "The email, phone, username, or password you entered is incorrect.",
  });
});

test("HTTP handlers use navigation hooks configured on the auth engine", async () => {
  const calls: string[] = [];
  const engine = createAuthEngine({
    store: new MemoryAuthStore(),
    secret: "engine-navigation-hooks-secret-longer-than-thirty-two-characters",
    onSignedIn(ctx, returnTo) {
      calls.push(`in:${returnTo}`);
      return Response.redirect(new URL(returnTo ?? "/account", ctx.url), 303);
    },
    onSignedOut(ctx) {
      calls.push("out");
      return Response.redirect(new URL("/sign-in", ctx.url), 303);
    },
  });
  await engine.register({
    email: "navigation@example.com",
    password: "StrongPassword123",
  });
  const handlers = createAuthHttpHandlers({ engine });
  const loginResponse = await handlers.login(
    context(
      new Request("https://example.test/api/auth/login", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          identifier: "navigation@example.com",
          password: "StrongPassword123",
          returnTo: "/dashboard",
        }),
      }),
    ),
  );
  expect(loginResponse.status).toBe(303);
  expect(loginResponse.headers.get("location")).toBe("https://example.test/dashboard");

  const logoutResponse = await handlers.logout(
    context(new Request("https://example.test/api/auth/logout", { method: "POST" })),
  );
  expect(logoutResponse.status).toBe(303);
  expect(logoutResponse.headers.get("location")).toBe("https://example.test/sign-in");
  expect(calls).toEqual(["in:/dashboard", "out"]);
});

test("register handler returns validation field errors before calling the engine", async () => {
  let registerCalls = 0;
  const engine = {
    register: async () => {
      registerCalls += 1;
      return { ok: true };
    },
  } as unknown as ReturnType<typeof createAuthEngine>;
  const handlers = createAuthHttpHandlers({ engine });
  const ctx = context(
    new Request("https://example.test/api/auth/register", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ displayName: "A", email: "bad", password: "short" }),
    }),
  );

  const response = await handlers.register(ctx);
  expect(response.status).toBe(400);
  expect(await response.json()).toMatchObject({
    ok: false,
    errors: {
      displayName: expect.any(String),
      email: expect.any(String),
      password: expect.any(String),
    },
  });
  expect(registerCalls).toBe(0);
});

test("successful signup redirects to sign-in by default", async () => {
  const engine = createAuthEngine({
    store: new MemoryAuthStore(),
    secret: "default-signup-redirect-secret-longer-than-thirty-two-characters",
  });
  const handlers = createAuthHttpHandlers({ engine });
  const response = await handlers.register(
    context(
      new Request("https://example.test/api/auth/register", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          displayName: "Default Redirect",
          email: "default-signup@example.com",
          password: "StrongPassword123",
          consent: true,
        }),
      }),
    ),
  );

  expect(response.status).toBe(303);
  expect(response.headers.get("location")).toBe("https://example.test/sign-in");
});

test("onSuccessfulSignUp can safely auto-sign-in and redirect the new user", async () => {
  const engine = createAuthEngine({
    store: new MemoryAuthStore(),
    secret: "automatic-signup-login-secret-longer-than-thirty-two-characters",
    onSuccessfulSignUp() {
      return { autoSignIn: true, redirectTo: "/welcome" };
    },
  });
  const handlers = createAuthHttpHandlers({ engine });
  const ctx = context(
    new Request("https://example.test/api/auth/register", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        displayName: "Automatic Login",
        email: "automatic-signup@example.com",
        password: "StrongPassword123",
        consent: true,
      }),
    }),
  );
  const response = await handlers.register(ctx);

  expect(response.status).toBe(303);
  expect(response.headers.get("location")).toBe("https://example.test/welcome");
  expect(ctx.locals.authUser).toMatchObject({ displayName: "Automatic Login" });
  expect(ctx.locals.authSession).toBeDefined();
});

test("signup auto-sign-in does not bypass verification policy", async () => {
  const engine = createAuthEngine({
    store: new MemoryAuthStore(),
    secret: "verified-signup-policy-secret-longer-than-thirty-two-characters",
    requireVerifiedEmail: true,
    onSuccessfulSignUp() {
      return { autoSignIn: true, redirectTo: "/account" };
    },
  });
  const handlers = createAuthHttpHandlers({ engine });
  const ctx = context(
    new Request("https://example.test/api/auth/register", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        displayName: "Verification Required",
        email: "verify-signup@example.com",
        password: "StrongPassword123",
        consent: true,
      }),
    }),
  );
  const response = await handlers.register(ctx);

  expect(response.status).toBe(401);
  expect(await response.json()).toMatchObject({
    ok: false,
    code: "email-unverified",
    message: "Verify your email address before signing in.",
  });
  expect(ctx.locals.authSession).toBeUndefined();
});

test("authenticated OTP issue rejects an unlinked destination without throwing", async () => {
  const engine = createAuthEngine({
    store: new MemoryAuthStore(),
    secret: "http-otp-secret-that-is-longer-than-thirty-two-characters",
  });
  const registered = await engine.register({
    email: "owner@example.com",
    password: "StrongPassword123",
  });
  const handlers = createAuthHttpHandlers({ engine });
  const ctx = context(
    new Request("https://example.test/api/auth/otp", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ method: "email-otp", destination: "other@example.com" }),
    }),
  );
  ctx.user = registered.user!;
  ctx.locals.authUser = registered.user!;
  const response = await handlers.issueOtp(ctx);
  expect(response.status).toBe(400);
  expect(await response.json()).toMatchObject({ ok: false });
});

test("verification resend is generic and can resolve an unauthenticated identifier", async () => {
  const messages: Array<{ template: string }> = [];
  const engine = createAuthEngine({
    store: new MemoryAuthStore(),
    secret: "verification-resend-secret-that-is-longer-than-thirty-two-characters",
    delivery: {
      async send(message) {
        messages.push(message);
      },
    },
  });
  await engine.register({
    email: "resend@example.com",
    password: "StrongPassword123",
  });
  const handlers = createAuthHttpHandlers({ engine });
  const before = messages.length;
  const known = await handlers.requestVerification(
    context(
      new Request("https://example.test/api/auth/verification/request", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ type: "email", identifier: "resend@example.com" }),
      }),
    ),
  );
  expect(known.status).toBe(200);
  expect(await known.json()).toEqual({ ok: true });
  expect(messages).toHaveLength(before + 1);

  const unknown = await handlers.requestVerification(
    context(
      new Request("https://example.test/api/auth/verification/request", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ type: "email", identifier: "missing@example.com" }),
      }),
    ),
  );
  expect(unknown.status).toBe(200);
  expect(await unknown.json()).toEqual({ ok: true });
  expect(messages).toHaveLength(before + 1);
});
