/**
 * Unit tests for setUserPassword (admin password reset).
 *
 * Tests cover:
 *   - Happy path: passwordHash is updated in the DB
 *   - Audit entry "Password reset by admin" is created
 *   - Non-existent user throws NOT_FOUND
 *   - Short password is rejected by Zod schema before reaching the function
 */

import { beforeEach, describe, expect, it, vi } from "vitest";
import { setUserPassword, SetUserPasswordInputSchema } from "../router/user-procedure-support.js";

// ── Mocks ────────────────────────────────────────────────────────────────────
vi.mock("../lib/audit.js", () => ({
  createAuditEntry: vi.fn(),
}));

const hashMock = vi.hoisted(() => vi.fn().mockResolvedValue("$argon2id$hashed"));

vi.mock("@node-rs/argon2", () => ({ hash: hashMock }));

// ── Helpers ──────────────────────────────────────────────────────────────────
function makeCtx(userRow: Record<string, unknown> | null = null) {
  return {
    db: {
      user: {
        findUnique: vi.fn().mockResolvedValue(userRow),
        update: vi.fn().mockResolvedValue({}),
      },
    } as never,
    dbUser: { id: "admin_1" },
  };
}

const EXISTING_USER = { id: "user_1", name: "Alice", email: "alice@example.com" };

// ── Tests ────────────────────────────────────────────────────────────────────

describe("setUserPassword — happy path", () => {
  beforeEach(() => { vi.clearAllMocks(); });

  it("hashes the new password and updates the DB", async () => {
    const ctx = makeCtx(EXISTING_USER);
    const result = await setUserPassword(ctx, { userId: "user_1", password: "NewPass123!" });

    expect(hashMock).toHaveBeenCalledWith("NewPass123!");
    expect(ctx.db.user.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "user_1" },
        data: { passwordHash: "$argon2id$hashed" },
      }),
    );
    expect(result).toEqual({ success: true });
  });

  it("creates an audit entry with summary 'Password reset by admin'", async () => {
    const { createAuditEntry } = await import("../lib/audit.js");
    const ctx = makeCtx(EXISTING_USER);
    await setUserPassword(ctx, { userId: "user_1", password: "NewPass123!" });

    // createAuditEntry is called fire-and-forget (void), so we give microtasks a tick
    await Promise.resolve();
    expect(createAuditEntry).toHaveBeenCalledWith(
      expect.objectContaining({ summary: "Password reset by admin" }),
    );
  });
});

describe("setUserPassword — not found", () => {
  beforeEach(() => { vi.clearAllMocks(); });

  it("throws when the user does not exist", async () => {
    const ctx = makeCtx(null); // findUnique returns null
    await expect(
      setUserPassword(ctx, { userId: "ghost", password: "NewPass123!" }),
    ).rejects.toThrow();
  });
});

describe("SetUserPasswordInputSchema — validation", () => {
  it("accepts a valid input", () => {
    const result = SetUserPasswordInputSchema.safeParse({ userId: "u1", password: "Valid123!" });
    expect(result.success).toBe(true);
  });

  it("rejects a password shorter than 8 characters", () => {
    const result = SetUserPasswordInputSchema.safeParse({ userId: "u1", password: "short" });
    expect(result.success).toBe(false);
  });

  it("rejects missing userId", () => {
    const result = SetUserPasswordInputSchema.safeParse({ password: "Valid123!" });
    expect(result.success).toBe(false);
  });
});
