import { createClient } from "@supabase/supabase-js";
import { testClient } from "hono/testing";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createConfig } from "../../config.js";
import { ensureActiveClientAccess, upsertClientByEmail } from "../../helpers/clientAccounts.js";
import { createClientMagicLink } from "../../helpers/clientMagicLinks.js";
import { signClientSession } from "../../helpers/clientSessions.js";
import { MiddlewareManager } from "../../middlewares/middleware.js";
import { getMainRouter } from "../../routers/index.js";
import { getTestUser } from "../helpers/dbSetup.js";

const mockSendMail = vi.fn();
vi.mock("nodemailer", () => ({
  default: {
    createTransport: vi.fn(() => ({
      sendMail: mockSendMail,
    })),
  },
  createTransport: vi.fn(() => ({
    sendMail: mockSendMail,
  })),
}));

const config = createConfig();
const supabaseAdmin = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY, {
  auth: { persistSession: false },
});
const { error: clientSchemaError } = await supabaseAdmin.from("clients").select("id").limit(1);
const hasClientAuthSchema =
  !clientSchemaError || (clientSchemaError as { code?: string }).code !== "PGRST205";

describe.skipIf(!hasClientAuthSchema)("Client Auth Endpoints", () => {
  MiddlewareManager.initialize(config);
  const app = getMainRouter(config);
  // biome-ignore lint/suspicious/noExplicitAny: testClient requires any for dynamic route access
  const client = testClient(app) as any;
  const ownerUser = getTestUser("owner");
  const adminTabloId = "test_tablo_owner_private";

  beforeEach(() => {
    vi.clearAllMocks();
    mockSendMail.mockResolvedValue({ messageId: "test-message-id" });
  });

  const cleanupClientAuthByEmail = async (email: string) => {
    const { data: clientRow } = await supabaseAdmin
      .from("clients")
      .select("id")
      .eq("normalized_email", email.toLowerCase())
      .maybeSingle();

    if (!clientRow?.id) {
      return;
    }

    await supabaseAdmin.from("client_magic_links").delete().eq("client_id", clientRow.id);
    await supabaseAdmin.from("client_access").delete().eq("client_id", clientRow.id);
    await supabaseAdmin.from("clients").delete().eq("id", clientRow.id);
  };

  const createClientWithAccess = async (email: string, tabloId = adminTabloId) => {
    const clientResult = await upsertClientByEmail(supabaseAdmin, email);
    if (!clientResult.client) {
      throw new Error(clientResult.error ?? "Failed to create client");
    }

    const accessResult = await ensureActiveClientAccess(supabaseAdmin, {
      clientId: clientResult.client.id,
      grantedBy: ownerUser.userId,
      tabloId,
    });

    if (!accessResult.success) {
      throw new Error(accessResult.error ?? "Failed to grant access");
    }

    return clientResult.client;
  };

  it("returns a neutral success response for request-link even when the email is unknown", async () => {
    const res = await client["client-auth"]["request-link"].$post({
      json: { email: "unknown-client@example.com" },
    });

    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.success).toBe(true);
    expect(data.message).toContain("If this email can access the client portal");
    expect(mockSendMail).not.toHaveBeenCalled();
  });

  it("creates and emails a login magic link when the client has active access", async () => {
    const email = "active-client@example.com";
    await cleanupClientAuthByEmail(email);
    const clientRow = await createClientWithAccess(email);

    const res = await client["client-auth"]["request-link"].$post({
      json: { email },
    });

    expect(res.status).toBe(200);
    expect(mockSendMail).toHaveBeenCalledTimes(1);

    const { data: links } = await supabaseAdmin
      .from("client_magic_links")
      .select("client_id, purpose, consumed_at")
      .eq("client_id", clientRow.id)
      .eq("purpose", "login");

    expect(links).toHaveLength(1);
    expect(links?.[0]?.consumed_at).toBeNull();

    const sentMail = mockSendMail.mock.calls[0]?.[0];
    expect(sentMail?.to).toBe(email);
    expect(sentMail?.html).toContain(`${config.CLIENTS_URL}/auth/exchange?token=`);
    expect(sentMail?.html).not.toContain(`${config.API_BASE_URL}/client-auth/exchange?token=`);
  });

  it("rejects an expired or consumed exchange token", async () => {
    const email = "expired-client@example.com";
    await cleanupClientAuthByEmail(email);
    const clientRow = await createClientWithAccess(email);

    const magicLinkResult = await createClientMagicLink(supabaseAdmin, {
      clientId: clientRow.id,
      email,
      expiresInMinutes: -1,
      jwtSecret: config.CLIENT_AUTH_JWT_SECRET,
      purpose: "login",
    });

    const res = await app.request(
      `http://localhost/client-auth/exchange?token=${encodeURIComponent(
        magicLinkResult.token ?? ""
      )}`
    );

    expect(res.status).toBe(410);
  });

  it("sets the client session cookie when a valid token is exchanged", async () => {
    const email = "exchange-client@example.com";
    await cleanupClientAuthByEmail(email);
    const clientRow = await createClientWithAccess(email);

    const magicLinkResult = await createClientMagicLink(supabaseAdmin, {
      clientId: clientRow.id,
      email,
      expiresInMinutes: 30,
      jwtSecret: config.CLIENT_AUTH_JWT_SECRET,
      purpose: "invite",
      redirectTo: `/tablo/${adminTabloId}`,
      tabloId: adminTabloId,
    });

    const res = await app.request(
      `http://localhost/client-auth/exchange?token=${encodeURIComponent(
        magicLinkResult.token ?? ""
      )}`
    );

    expect(res.status).toBe(302);
    expect(res.headers.get("set-cookie")).toContain(config.CLIENT_AUTH_COOKIE_NAME);
    expect(res.headers.get("location")).toBe(`${config.CLIENTS_URL}/tablo/${adminTabloId}`);
  });

  it("clears the cookie on logout", async () => {
    const res = await client["client-auth"].logout.$post();

    expect(res.status).toBe(200);
    expect(res.headers.get("set-cookie")).toContain(`${config.CLIENT_AUTH_COOKIE_NAME}=;`);
  });

  it("returns the current client from /me when the cookie is valid", async () => {
    const email = "me-client@example.com";
    await cleanupClientAuthByEmail(email);
    const clientRow = await createClientWithAccess(email);
    const token = signClientSession(
      {
        clientId: clientRow.id,
        email,
      },
      {
        expiresInDays: config.CLIENT_SESSION_TTL_DAYS,
        secret: config.CLIENT_AUTH_JWT_SECRET,
      }
    );

    const res = await app.request("http://localhost/client-auth/me", {
      headers: {
        Cookie: `${config.CLIENT_AUTH_COOKIE_NAME}=${token}`,
      },
    });

    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.client.id).toBe(clientRow.id);
    expect(data.client.email).toBe(email);
  });
});
