import { beforeEach, describe, expect, test } from "vitest";
import { db, sqlite } from "~/server/db/db";
import { account, invitation, member, organization, sessionsTable, ssoProvider, usersTable } from "~/server/db/schema";
import {
	createOrganization,
	createSession,
	createUser,
	dropTrigger,
	escapeSqlLiteral,
	randomId,
	randomSlug,
} from "~/test/helpers/user-org";
import { ssoService } from "../sso.service";

const DELETE_SSO_PROVIDER_ROLLBACK_TRIGGER = "delete_sso_provider_sessions_abort";

describe("ssoService.deleteSsoProvider", () => {
	beforeEach(async () => {
		dropTrigger(DELETE_SSO_PROVIDER_ROLLBACK_TRIGGER);
		await db.delete(member);
		await db.delete(account);
		await db.delete(sessionsTable);
		await db.delete(invitation);
		await db.delete(ssoProvider);
		await db.delete(organization);
		await db.delete(usersTable);
	});

	test("does not delete accounts when provider belongs to another organization", async () => {
		const orgA = await createOrganization("Org A");
		const orgB = await createOrganization("Org B");
		const providerOwner = await createUser(`${randomSlug("owner")}@example.com`);
		const accountUser = await createUser(`${randomSlug("member")}@example.com`);

		const providerId = `oidc-${randomSlug("provider")}`;

		await db.insert(ssoProvider).values({
			id: randomId(),
			providerId,
			organizationId: orgB,
			userId: providerOwner,
			issuer: "https://issuer.example.com",
			domain: "example.com",
		});

		await db.insert(account).values({
			id: randomId(),
			accountId: randomSlug("acct"),
			providerId,
			userId: accountUser,
		});

		const deleted = await ssoService.deleteSsoProvider(providerId, orgA);

		expect(deleted).toBe(false);

		const remainingProvider = await db.query.ssoProvider.findFirst({
			where: { providerId },
			columns: { id: true },
		});
		const remainingAccounts = await db.query.account.findMany({
			where: { providerId },
			columns: { id: true },
		});

		expect(remainingProvider).not.toBeUndefined();
		expect(remainingAccounts).toHaveLength(1);
	});

	test("deletes provider and linked accounts in the active organization", async () => {
		const org = await createOrganization("Org A");
		const providerOwner = await createUser(`${randomSlug("owner")}@example.com`);
		const accountUserA = await createUser(`${randomSlug("member-a")}@example.com`);
		const accountUserB = await createUser(`${randomSlug("member-b")}@example.com`);

		const providerId = `oidc-${randomSlug("provider")}`;

		await db.insert(ssoProvider).values({
			id: randomId(),
			providerId,
			organizationId: org,
			userId: providerOwner,
			issuer: "https://issuer.example.com",
			domain: "example.com",
		});

		await db.insert(account).values([
			{
				id: randomId(),
				accountId: randomSlug("acct-a"),
				providerId,
				userId: accountUserA,
			},
			{
				id: randomId(),
				accountId: randomSlug("acct-b"),
				providerId,
				userId: accountUserB,
			},
		]);
		await createSession({ userId: accountUserA });
		await createSession({ userId: accountUserB });

		const deleted = await ssoService.deleteSsoProvider(providerId, org);

		expect(deleted).toBe(true);

		const remainingProvider = await db.query.ssoProvider.findFirst({
			where: { providerId },
			columns: { id: true },
		});
		const remainingAccounts = await db.query.account.findMany({
			where: { providerId },
			columns: { id: true },
		});
		const remainingSessions = await db.query.sessionsTable.findMany({
			where: { userId: { in: [accountUserA, accountUserB] } },
			columns: { id: true },
		});

		expect(remainingProvider).toBeUndefined();
		expect(remainingAccounts).toHaveLength(0);
		expect(remainingSessions).toHaveLength(0);
	});

	test("deleting a reserved provider id never deletes credential accounts", async () => {
		const org = await createOrganization("Org A");
		const providerOwner = await createUser(`${randomSlug("owner")}@example.com`);
		const credentialUser = await createUser(`${randomSlug("credential")}@example.com`);

		await db.insert(ssoProvider).values({
			id: randomId(),
			providerId: "credential",
			organizationId: org,
			userId: providerOwner,
			issuer: "https://issuer.example.com",
			domain: "example.com",
		});

		await db.insert(account).values({
			id: randomId(),
			accountId: randomSlug("credential-acct"),
			providerId: "credential",
			userId: credentialUser,
		});

		const deleted = await ssoService.deleteSsoProvider("credential", org);

		expect(deleted).toBe(true);

		const remainingProvider = await db.query.ssoProvider.findFirst({
			where: { providerId: "credential" },
			columns: { id: true },
		});
		const remainingAccounts = await db.query.account.findMany({
			where: { providerId: "credential" },
			columns: { id: true },
		});

		expect(remainingProvider).toBeUndefined();
		expect(remainingAccounts).toHaveLength(1);
	});

	test("deleteSsoProvider rolls back account and provider deletion when session revocation fails", async () => {
		const org = await createOrganization("Org A");
		const providerOwner = await createUser(`${randomSlug("owner")}@example.com`);
		const accountUser = await createUser(`${randomSlug("member")}@example.com`);

		const providerId = `oidc-${randomSlug("provider")}`;

		await db.insert(ssoProvider).values({
			id: randomId(),
			providerId,
			organizationId: org,
			userId: providerOwner,
			issuer: "https://issuer.example.com",
			domain: "example.com",
		});

		await db.insert(account).values({
			id: randomId(),
			accountId: randomSlug("acct"),
			providerId,
			userId: accountUser,
		});
		await createSession({ userId: accountUser });

		sqlite.exec(`
			CREATE TRIGGER ${DELETE_SSO_PROVIDER_ROLLBACK_TRIGGER}
			BEFORE DELETE ON sessions_table
			WHEN OLD.user_id = '${escapeSqlLiteral(accountUser)}'
			BEGIN
				SELECT RAISE(ABORT, 'forced deleteSsoProvider rollback');
			END;
		`);

		try {
			await expect(ssoService.deleteSsoProvider(providerId, org)).rejects.toThrow("forced deleteSsoProvider rollback");
		} finally {
			dropTrigger(DELETE_SSO_PROVIDER_ROLLBACK_TRIGGER);
		}

		const remainingProvider = await db.query.ssoProvider.findFirst({
			where: { providerId },
			columns: { id: true },
		});
		const remainingAccounts = await db.query.account.findMany({
			where: { providerId },
			columns: { id: true },
		});
		const remainingSessions = await db.query.sessionsTable.findMany({
			where: { userId: accountUser },
			columns: { id: true },
		});

		expect(remainingProvider).not.toBeUndefined();
		expect(remainingAccounts).toHaveLength(1);
		expect(remainingSessions).toHaveLength(1);
	});
});
