import { beforeEach, describe, expect, test, vi } from "vitest";

vi.mock("better-auth/crypto", () => ({
	hashPassword: vi.fn(async () => "test-account-password-hash"),
}));

import { convertLegacyUserOnFirstLogin } from "../convert-legacy-user";
import { db } from "~/server/db/db";
import { usersTable, account, organization, member } from "~/server/db/schema";
import type { AuthMiddlewareContext } from "~/server/lib/auth";

describe("convertLegacyUserOnFirstLogin", () => {
	const legacyPasswordHash = "legacy-password-hash";
	const verifyPassword = vi.spyOn(Bun.password, "verify");

	beforeEach(async () => {
		verifyPassword.mockReset();
		verifyPassword.mockImplementation(async (password) => password === "correct-password");

		await db.delete(member);
		await db.delete(account);
		await db.delete(organization);
		await db.delete(usersTable);
	});

	const createContext = (path: string, body: Record<string, string>) => ({ path, body }) as AuthMiddlewareContext;

	test("should return early for non-sign-in paths", async () => {
		const ctx = createContext("/sign-up", { username: "test", password: "test" });
		const result = await convertLegacyUserOnFirstLogin(ctx);
		expect(result).toBeUndefined();
	});

	test("should do nothing when no legacy user exists", async () => {
		await db.insert(usersTable).values({
			id: crypto.randomUUID(),
			username: "existing-user",
			email: "existing@test.com",
			name: "Existing User",
			passwordHash: null,
		});

		const ctx = createContext("/sign-in/username", {
			username: "existing-user",
			password: "password123",
		});

		const result = await convertLegacyUserOnFirstLogin(ctx);
		expect(result).toBeUndefined();

		// Verify user still exists with no account
		const user = await db.query.usersTable.findFirst({
			where: { username: "existing-user" },
		});
		expect(user).toBeDefined();
		expect(user?.passwordHash).toBeNull();
	});

	test("should throw UnauthorizedError for invalid password", async () => {
		// Create a legacy user with a hashed password
		const userId = crypto.randomUUID();
		await db.insert(usersTable).values({
			id: userId,
			username: "legacy-user",
			email: "legacy@test.com",
			name: "Legacy User",
			passwordHash: legacyPasswordHash,
		});

		const ctx = createContext("/sign-in/username", {
			username: "legacy-user",
			password: "wrong-password",
		});

		await expect(convertLegacyUserOnFirstLogin(ctx)).rejects.toThrow("Invalid credentials");

		// Verify user still exists (not migrated)
		const user = await db.query.usersTable.findFirst({
			where: { username: "legacy-user" },
		});
		expect(user).toBeDefined();
		expect(user?.passwordHash).toBe(legacyPasswordHash);
	});

	test("should migrate legacy user with existing organization membership", async () => {
		const password = "correct-password";

		// Create legacy user
		const userId = crypto.randomUUID();
		await db.insert(usersTable).values({
			id: userId,
			username: "legacy-with-org",
			email: "legacy-org@test.com",
			name: "Legacy With Org",
			passwordHash: legacyPasswordHash,
			role: "admin",
		});

		// Create organization and membership
		const orgId = crypto.randomUUID();
		await db.insert(organization).values({
			id: orgId,
			name: "Legacy Org",
			slug: "legacy-org",
			createdAt: new Date(),
		});

		const membershipId = crypto.randomUUID();
		await db.insert(member).values({
			id: membershipId,
			userId: userId,
			organizationId: orgId,
			role: "owner",
			createdAt: new Date(),
		});

		const ctx = createContext("/sign-in/username", {
			username: "legacy-with-org",
			password,
		});

		await convertLegacyUserOnFirstLogin(ctx);

		// Verify old user is deleted
		const oldUser = await db.query.usersTable.findFirst({
			where: { id: userId },
		});
		expect(oldUser).toBeUndefined();

		// Verify new user exists
		const newUser = await db.query.usersTable.findFirst({
			where: { username: "legacy-with-org" },
		});
		expect(newUser).toBeDefined();
		expect(newUser?.email).toBe("legacy-org@test.com");
		expect(newUser?.name).toBe("Legacy With Org");
		expect(newUser?.role).toBe("admin");
		expect(newUser?.passwordHash).toBeNull();
		expect(newUser?.id).not.toBe(userId);

		// Verify account was created
		const userAccount = await db.query.account.findFirst({
			where: { userId: newUser?.id },
		});
		expect(userAccount).toBeDefined();
		expect(userAccount?.providerId).toBe("credential");
		expect(userAccount?.accountId).toBe("legacy-with-org");
		expect(userAccount?.password).toBeDefined();

		// Verify membership was migrated
		const memberships = await db.query.member.findMany({
			where: { userId: newUser?.id },
		});
		expect(memberships.length).toBe(1);
		expect(memberships[0].organizationId).toBe(orgId);
		expect(memberships[0].role).toBe("owner");
	});

	test("should migrate legacy user and create new organization when no membership exists", async () => {
		const password = "correct-password";

		// Create legacy user without organization membership
		const userId = crypto.randomUUID();
		await db.insert(usersTable).values({
			id: userId,
			username: "legacy-no-org",
			email: "legacy-noorg@test.com",
			name: "Legacy No Org",
			passwordHash: legacyPasswordHash,
			hasDownloadedResticPassword: true,
		});

		const ctx = createContext("/sign-in/username", {
			username: "legacy-no-org",
			password,
		});

		await convertLegacyUserOnFirstLogin(ctx);

		// Verify old user is deleted
		const oldUser = await db.query.usersTable.findFirst({
			where: { id: userId },
		});
		expect(oldUser).toBeUndefined();

		// Verify new user exists
		const newUser = await db.query.usersTable.findFirst({
			where: { username: "legacy-no-org" },
		});
		expect(newUser).toBeDefined();
		expect(newUser?.email).toBe("legacy-noorg@test.com");
		expect(newUser?.hasDownloadedResticPassword).toBe(true);
		expect(newUser?.role).toBe("admin");

		// Verify account was created
		const userAccount = await db.query.account.findFirst({
			where: { userId: newUser?.id },
		});
		expect(userAccount).toBeDefined();

		// Verify new organization was created
		const memberships = await db.query.member.findMany({
			where: { userId: newUser?.id },
		});
		expect(memberships.length).toBe(1);
		expect(memberships[0].role).toBe("owner");

		const org = await db.query.organization.findFirst({
			where: { id: memberships[0].organizationId },
		});
		expect(org).toBeDefined();
		expect(org?.name).toBe("Legacy No Org's Workspace");
		expect(org?.metadata).toBeDefined();
	});

	test("should be case-insensitive for username", async () => {
		const password = "correct-password";

		const userId = crypto.randomUUID();
		await db.insert(usersTable).values({
			id: userId,
			username: "legacy-user",
			email: "legacy@test.com",
			name: "Legacy User",
			passwordHash: legacyPasswordHash,
		});

		// Try login with uppercase username
		const ctx = createContext("/sign-in/username", {
			username: "LEGACY-USER",
			password,
		});

		await convertLegacyUserOnFirstLogin(ctx);

		// Verify migration happened
		const oldUser = await db.query.usersTable.findFirst({
			where: { id: userId },
		});
		expect(oldUser).toBeUndefined();

		const newUser = await db.query.usersTable.findFirst({
			where: { username: "legacy-user" },
		});
		expect(newUser).toBeDefined();
	});

	test("should trim whitespace from username", async () => {
		const password = "correct-password";

		const userId = crypto.randomUUID();
		await db.insert(usersTable).values({
			id: userId,
			username: "legacy-user",
			email: "legacy@test.com",
			name: "Legacy User",
			passwordHash: legacyPasswordHash,
		});

		// Try login with whitespace
		const ctx = createContext("/sign-in/username", {
			username: "  legacy-user  ",
			password,
		});

		await convertLegacyUserOnFirstLogin(ctx);

		// Verify migration happened
		const oldUser = await db.query.usersTable.findFirst({
			where: { id: userId },
		});
		expect(oldUser).toBeUndefined();

		const newUser = await db.query.usersTable.findFirst({
			where: { username: "legacy-user" },
		});
		expect(newUser).toBeDefined();
	});
});
