/**
 * Unit tests for `resetAdminPassword` in `src/actions/admin/reset-admin.ts`.
 *
 * The action must:
 *   1. Reject unauthenticated callers and non-platform-admins
 *   2. Look up `neon_auth.user` by email (NOT the legacy `users` table)
 *   3. Try `auth.admin.setUserPassword` first (instant credential)
 *   4. Fall back to `auth.requestPasswordReset` when the admin
 *      endpoint returns FORBIDDEN / UNAUTHORIZED / INTERNAL_SERVER_ERROR
 *   5. On success, set `must_change_password = true` for the user
 *   6. Generate a strong server-side random password (no hard-coded
 *      defaults like "Tuxedo2026!")
 *   7. Return clear errors when no Neon Auth user matches the email
 */
import { describe, it, expect, vi, beforeEach } from "vitest";

const {
  mockGetAdminUser,
  mockQuery,
  mockSetUserPassword,
  mockRequestPasswordReset,
} = vi.hoisted(() => ({
  mockGetAdminUser: vi.fn(),
  mockQuery: vi.fn(),
  mockSetUserPassword: vi.fn(),
  mockRequestPasswordReset: vi.fn(),
}));

vi.mock("server-only", () => ({}));

vi.mock("@/lib/admin-permissions", () => ({
  getAdminUser: mockGetAdminUser,
}));

vi.mock("@/lib/auth", () => ({
  setUserPassword: mockSetUserPassword,
  requestPasswordReset: mockRequestPasswordReset,
}));

vi.mock("@/lib/db", () => ({
  query: mockQuery,
}));

vi.mock("next/headers", () => ({
  cookies: () => Promise.resolve({ get: () => undefined }),
}));

import { resetAdminPassword } from "@/actions/admin/reset-admin";

const PLATFORM_ADMIN = {
  id: "platform-1",
  user_id: "platform-1",
  email: "root@example.com",
  display_name: "Root",
  brand_id: null,
  brand_ids: [],
  brand_slug: null,
  role: "platform_admin" as const,
  active: true,
  auth_provider: "neon_auth" as const,
  can_manage_products: true,
  can_manage_stops: true,
  can_manage_orders: true,
  can_manage_pickup: true,
  can_manage_messages: true,
  can_manage_refunds: true,
  can_manage_users: true,
  can_manage_water_log: true,
  can_manage_reports: true,
  can_manage_settings: true,
  can_manage_billing: true,
  can_manage_branding: true,
  can_manage_marketing: true,
  can_manage_team: true,
  must_change_password: false,
};

const BRAND_ADMIN = {
  ...PLATFORM_ADMIN,
  role: "brand_admin" as const,
  brand_id: "brand-tux",
  brand_ids: ["brand-tux"],
  brand_slug: "tuxedo",
};

beforeEach(() => {
  vi.clearAllMocks();
  process.env.NEXT_PUBLIC_SITE_URL = "http://localhost:4000";
  mockGetAdminUser.mockResolvedValue(PLATFORM_ADMIN);
  // Default: Neon Auth user lookup succeeds.
  mockQuery.mockImplementation(async (sql: string) => {
    if (sql.includes("SELECT id FROM neon_auth.user")) {
      return { rows: [{ id: "neon-user-1" }] };
    }
    // UPDATE admin_users SET must_change_password — accept silently.
    return { rows: [], rowCount: 1 };
  });
  // Default: setUserPassword succeeds.
  mockSetUserPassword.mockResolvedValue({ data: { ok: true }, error: null });
  mockRequestPasswordReset.mockResolvedValue({ data: { ok: true }, error: null });
});

describe("resetAdminPassword — authorization", () => {
  it("rejects unauthenticated callers", async () => {
    mockGetAdminUser.mockResolvedValue(null);
    const r = await resetAdminPassword("user@example.com");
    expect(r.success).toBe(false);
    if (r.success) throw new Error("expected failure");
    expect(r.error).toMatch(/not authenticated/i);
    expect(mockSetUserPassword).not.toHaveBeenCalled();
    expect(mockRequestPasswordReset).not.toHaveBeenCalled();
  });

  it("rejects non-platform-admin callers", async () => {
    mockGetAdminUser.mockResolvedValue(BRAND_ADMIN);
    const r = await resetAdminPassword("user@example.com");
    expect(r.success).toBe(false);
    if (r.success) throw new Error("expected failure");
    expect(r.error).toMatch(/only platform admins/i);
    expect(mockSetUserPassword).not.toHaveBeenCalled();
    expect(mockRequestPasswordReset).not.toHaveBeenCalled();
  });
});

describe("resetAdminPassword — input handling", () => {
  it("rejects an empty email", async () => {
    const r = await resetAdminPassword("   ");
    expect(r.success).toBe(false);
    if (r.success) throw new Error("expected failure");
    expect(r.error).toMatch(/required/i);
  });

  it("looks up the user in neon_auth.user (not the legacy `users` table)", async () => {
    const r = await resetAdminPassword("User@Example.COM");
    expect(r.success).toBe(true);
    expect(mockQuery).toHaveBeenCalledWith(
      "SELECT id FROM neon_auth.user WHERE email = $1 LIMIT 1",
      ["user@example.com"],
    );
  });

  it("returns a clear error when no Neon Auth user matches the email", async () => {
    mockQuery.mockImplementation(async (sql: string) => {
      if (sql.includes("SELECT id FROM neon_auth.user")) {
        return { rows: [] };
      }
      return { rows: [], rowCount: 0 };
    });
    const r = await resetAdminPassword("missing@example.com");
    expect(r.success).toBe(false);
    if (r.success) throw new Error("expected failure");
    expect(r.error).toMatch(/no neon auth user found/i);
    expect(mockSetUserPassword).not.toHaveBeenCalled();
  });
});

describe("resetAdminPassword — happy path via setUserPassword", () => {
  it("returns a server-generated temp password and flips must_change_password", async () => {
    const r = await resetAdminPassword("user@example.com");
    expect(r.success).toBe(true);
    if (!r.success) throw new Error("expected success");
    if (r.method !== "set") throw new Error("expected method=set");
    // The password is server-generated — must be a non-empty strong
    // string, never a hard-coded constant like "Tuxedo2026!".
    expect(r.tempPassword.length).toBeGreaterThan(20);
    expect(r.tempPassword).not.toBe("Tuxedo2026!");
    expect(r.tempPassword).toMatch(/[!@#$%^&*]/); // includes a symbol

    expect(mockSetUserPassword).toHaveBeenCalledTimes(1);
    const [args] = mockSetUserPassword.mock.calls[0];
    expect(args.userId).toBe("neon-user-1");
    expect(args.newPassword).toBe(r.tempPassword);

    // The must_change_password flag should be flipped.
    const flagUpdate = mockQuery.mock.calls.find(([sql]) =>
      String(sql).includes("UPDATE admin_users SET must_change_password"),
    );
    expect(flagUpdate).toBeTruthy();
  });
});

describe("resetAdminPassword — FORBIDDEN fallback to requestPasswordReset", () => {
  it("falls back to the public reset-email endpoint when setUserPassword returns FORBIDDEN", async () => {
    mockSetUserPassword.mockResolvedValue({
      data: null,
      error: { code: "FORBIDDEN", message: "Admin role required" },
    });
    const r = await resetAdminPassword("user@example.com");
    expect(r.success).toBe(true);
    if (!r.success) throw new Error("expected success");
    if (r.method !== "reset_link_sent") throw new Error("expected method=reset_link_sent");
    expect(r.message).toMatch(/reset email/i);
    expect(mockRequestPasswordReset).toHaveBeenCalledWith({
      email: "user@example.com",
      redirectTo: "http://localhost:4000/reset-password",
    });
    // The fallback case returns method="reset_link_sent" with a
    // `message` describing what was sent; TypeScript guarantees
    // tempPassword is not on this variant.
  });

  it("falls back when setUserPassword returns UNAUTHORIZED", async () => {
    mockSetUserPassword.mockResolvedValue({
      data: null,
      error: { code: "UNAUTHORIZED", message: "no session" },
    });
    const r = await resetAdminPassword("user@example.com");
    expect(r.success).toBe(true);
    if (!r.success) throw new Error("expected success");
    if (r.method !== "reset_link_sent") throw new Error("expected method=reset_link_sent");
  });

  it("falls back when setUserPassword throws", async () => {
    mockSetUserPassword.mockRejectedValue(new Error("network down"));
    const r = await resetAdminPassword("user@example.com");
    expect(r.success).toBe(true);
    if (!r.success) throw new Error("expected success");
    if (r.method !== "reset_link_sent") throw new Error("expected method=reset_link_sent");
  });

  it("does NOT fall back for USER_NOT_FOUND — it surfaces the error", async () => {
    mockSetUserPassword.mockResolvedValue({
      data: null,
      error: { code: "USER_NOT_FOUND", message: "no such user in neon auth" },
    });
    const r = await resetAdminPassword("user@example.com");
    expect(r.success).toBe(false);
    if (r.success) throw new Error("expected failure");
    expect(r.error).toMatch(/no such user in neon auth/);
    expect(mockRequestPasswordReset).not.toHaveBeenCalled();
  });

  it("propagates the public-endpoint error if BOTH paths fail", async () => {
    mockSetUserPassword.mockResolvedValue({
      data: null,
      error: { code: "FORBIDDEN" },
    });
    mockRequestPasswordReset.mockResolvedValue({
      data: null,
      error: { code: "RATE_LIMITED", message: "too many requests" },
    });
    const r = await resetAdminPassword("user@example.com");
    expect(r.success).toBe(false);
    if (r.success) throw new Error("expected failure");
    expect(r.error).toMatch(/too many requests/);
  });
});
