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

const dbMock = vi.hoisted(() => {
	const selectQueue: unknown[][] = [];
	const updateWhere = vi.fn();
	const updateSet = vi.fn((_value: unknown) => ({
		where: updateWhere,
	}));
	const where = vi.fn(() => ({
		limit: vi.fn(() => selectQueue.shift() ?? []),
	}));
	const innerJoin = vi.fn(() => ({ where }));
	const from = vi.fn(() => ({ innerJoin, where }));
	const select = vi.fn(() => ({ from }));
	const update = vi.fn(() => ({ set: updateSet }));

	return {
		selectQueue,
		select,
		from,
		innerJoin,
		where,
		update,
		updateSet,
		updateWhere,
	};
});

vi.mock("@reactive-resume/db/client", () => ({
	db: {
		select: dbMock.select,
		update: dbMock.update,
	},
}));

const { createGithubProfileMapper, createProfileMapper } = await import("./oauth-profile");

beforeEach(() => {
	dbMock.selectQueue.length = 0;
	dbMock.select.mockClear();
	dbMock.from.mockClear();
	dbMock.innerJoin.mockClear();
	dbMock.where.mockClear();
	dbMock.update.mockClear();
	dbMock.updateSet.mockClear();
	dbMock.updateWhere.mockClear();
});

describe("createProfileMapper", () => {
	it("reuses a migrated GitHub account when the login and email match the legacy account", async () => {
		dbMock.selectQueue.push([
			{
				id: "user-1",
				accountId: "legacy-user-id",
				email: "Legacy.User@Example.COM",
				emailVerified: true,
				username: "octo-cat",
				displayUsername: "Octo-Cat",
				name: "Legacy User",
				image: "https://example.com/old.png",
			},
		]);

		const mapper = createGithubProfileMapper();

		const result = await mapper({
			id: "123456",
			email: "legacy.user@example.com",
			login: "Octo-Cat",
			name: "Provider Name",
			avatar_url: "https://example.com/new.png",
		});

		expect(dbMock.select).toHaveBeenCalledTimes(1);
		expect(dbMock.update).toHaveBeenCalledTimes(1);
		expect(dbMock.updateSet).toHaveBeenCalledWith({ email: "legacy.user@example.com" });
		expect(result).toEqual({
			id: "user-1",
			name: "Legacy User",
			email: "legacy.user@example.com",
			image: "https://example.com/new.png",
			username: "octo-cat",
			displayUsername: "Octo-Cat",
			emailVerified: true,
		});
	});

	it("does not reuse a migrated GitHub account when only the login matches", async () => {
		dbMock.selectQueue.push(
			[
				{
					id: "user-1",
					accountId: "legacy-user-id",
					email: "Legacy.User@Example.COM",
					emailVerified: true,
					username: "octo-cat",
					displayUsername: "Octo-Cat",
					name: "Legacy User",
					image: "https://example.com/old.png",
				},
			],
			[],
			[{ id: "user-1" }],
			[],
		);

		const mapper = createGithubProfileMapper();

		const result = await mapper({
			id: "123456",
			email: "current.github@example.com",
			login: "Octo-Cat",
			name: "Provider Name",
			avatar_url: "https://example.com/new.png",
		});

		expect(dbMock.select).toHaveBeenCalledTimes(4);
		expect(dbMock.update).not.toHaveBeenCalled();
		expect(result).toEqual({
			name: "Provider Name",
			email: "current.github@example.com",
			image: "https://example.com/new.png",
			username: "octo-cat-1",
			displayUsername: "octo-cat-1",
			emailVerified: true,
		});
	});

	it("falls back to email matching when no migrated GitHub account matches the provider login", async () => {
		dbMock.selectQueue.push(
			[],
			[
				{
					id: "user-1",
					email: "GitHub.User@Example.COM",
					emailVerified: false,
					username: "github.user",
					displayUsername: "github.user",
					name: "GitHub User",
					image: null,
				},
			],
		);

		const mapper = createGithubProfileMapper();

		const result = await mapper({
			id: "123456",
			email: "github.user@example.com",
			login: "other-login",
			avatar_url: "https://example.com/new.png",
		});

		expect(dbMock.select).toHaveBeenCalledTimes(2);
		expect(result).toEqual({
			id: "user-1",
			name: "GitHub User",
			email: "github.user@example.com",
			image: "https://example.com/new.png",
			username: "github.user",
			displayUsername: "github.user",
			emailVerified: false,
		});
	});

	it("normalizes a matched legacy user email before handing it to Better Auth", async () => {
		dbMock.selectQueue.push([
			{
				id: "user-1",
				email: "Legacy.User@Example.COM",
				emailVerified: false,
				username: "legacy.user",
				displayUsername: "legacy.user",
				name: "Legacy User",
				image: "https://example.com/old.png",
			},
		]);

		const mapper = createProfileMapper({
			providerName: "Google",
			getImage: (profile) => profile.picture,
		});

		const result = await mapper({
			email: "legacy.user@example.com",
			name: "Provider Name",
			picture: "https://example.com/new.png",
		});

		expect(dbMock.update).toHaveBeenCalledTimes(1);
		expect(dbMock.updateSet).toHaveBeenCalledWith({ email: "legacy.user@example.com" });
		expect(result).toEqual({
			id: "user-1",
			name: "Legacy User",
			email: "legacy.user@example.com",
			image: "https://example.com/new.png",
			username: "legacy.user",
			displayUsername: "legacy.user",
			emailVerified: false,
		});
	});

	it("does not update the user row when the matched email is already normalized", async () => {
		dbMock.selectQueue.push([
			{
				id: "user-1",
				email: "user@example.com",
				emailVerified: true,
				username: "user",
				displayUsername: "user",
				name: "User",
				image: null,
			},
		]);

		const mapper = createProfileMapper({ providerName: "Google" });

		const result = await mapper({ email: "USER@example.com", name: "Provider User" });

		expect(dbMock.update).not.toHaveBeenCalled();
		expect(result.email).toBe("user@example.com");
		expect(result.name).toBe("User");
	});

	it("allocates a provider username for a new social user", async () => {
		dbMock.selectQueue.push([], []);

		const mapper = createProfileMapper({
			providerName: "GitHub",
			getPreferredUsername: (profile) => profile.login,
			getName: (profile) => profile.name,
			getImage: (profile) => profile.avatar_url,
		});

		const result = await mapper({
			email: "New.User@Example.com",
			login: "Octo-Cat",
			name: "Octo Cat",
			avatar_url: "https://example.com/avatar.png",
		});

		expect(result).toEqual({
			name: "Octo Cat",
			email: "new.user@example.com",
			image: "https://example.com/avatar.png",
			username: "octo-cat",
			displayUsername: "octo-cat",
			emailVerified: true,
		});
	});

	it("rejects provider profiles without an email address", async () => {
		const mapper = createProfileMapper({ providerName: "GitHub" });

		await expect(mapper({ login: "octocat" })).rejects.toThrow("GitHub provider did not return an email address");
	});
});
