import { DatabaseSync } from "node:sqlite";
import { NodeSqliteDialect } from "@better-auth/kysely-adapter/node-sqlite-dialect";
import { oauthProvider } from "@better-auth/oauth-provider";
import { passkey } from "@better-auth/passkey";
import { sso } from "@better-auth/sso";
import { stripe } from "@better-auth/stripe";
import { LibsqlDialect } from "@libsql/kysely-libsql";
import type { BetterAuthOptions } from "better-auth";
import { APIError, betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
import type { Organization } from "better-auth/plugins";
import {
	admin,
	bearer,
	customSession,
	deviceAuthorization,
	jwt,
	lastLoginMethod,
	magicLink,
	multiSession,
	openAPI,
	organization,
	twoFactor,
} from "better-auth/plugins";
import { Stripe } from "stripe";
import { reactInvitationEmail } from "./email/invitation.tsx";
import { reactMagicLinkEmail } from "./email/magic-link.tsx";
import { reactResetPasswordEmail } from "./email/reset-password.tsx";
import { sendEmail } from "./email/send.ts";
import { reactTwoFactorOtpEmail } from "./email/two-factor-otp.tsx";
import { reactVerifyEmail } from "./email/verify-email.tsx";
import {
	createSCIMDemoPlugin,
	isSCIMDemoEmployeePortalEnabled,
	isSCIMDemoEnabled,
} from "./scim-demo.ts";
import { resolveSCIMDemoSSOUser } from "./scim-demo-employee.ts";
import { getSCIMDemoOIDCProvider } from "./scim-demo-oidc.ts";

const APP_NAME = "Better Auth Demo";
const DEMO_SQLITE_CONTENTION_TIMEOUT_MS = 5_000;

/** URL publique de l'app, sans slash final (BETTER_AUTH_URL). */
const baseURL = (process.env.BETTER_AUTH_URL ?? "http://localhost:3000").replace(
	/\/+$/,
	"",
);

/**
 * Sélection de la base :
 * - DEMO_SQLITE_PATH  → SQLite natif Node (node:sqlite), utilisé par les tests e2e.
 * - TURSO_DATABASE_URL → libsql : `file:./data/local.db` en local (sans token),
 *   `libsql://…turso.io` + TURSO_AUTH_TOKEN pour Turso Cloud.
 */
const database = (() => {
	if (process.env.DEMO_SQLITE_PATH) {
		return {
			dialect: new NodeSqliteDialect({
				database: new DatabaseSync(process.env.DEMO_SQLITE_PATH, {
					timeout: DEMO_SQLITE_CONTENTION_TIMEOUT_MS,
				}),
			}),
			type: "sqlite" as const,
			transaction: true,
		};
	}
	if (process.env.TURSO_DATABASE_URL) {
		return {
			dialect: new LibsqlDialect({
				url: process.env.TURSO_DATABASE_URL,
				authToken: process.env.TURSO_AUTH_TOKEN || undefined,
			}),
			type: "sqlite" as const,
			transaction: true,
		};
	}
	throw new Error(
		"Aucune base configurée : définis TURSO_DATABASE_URL (ex. file:./data/local.db) ou DEMO_SQLITE_PATH dans .env",
	);
})();

const scimDemoPlugins = isSCIMDemoEnabled() ? [createSCIMDemoPlugin()] : [];

const authOptions = {
	appName: APP_NAME,
	database,
	user: {
		additionalFields: {
			scimDemoRole: {
				type: "string",
				required: false,
				input: false,
				returned: false,
			},
		},
	},
	emailVerification: {
		async sendVerificationEmail({ user, url }) {
			await sendEmail({
				to: user.email,
				subject: "Verify your email address",
				react: reactVerifyEmail({
					username: user.name,
					verificationUrl: url,
				}),
			});
		},
	},
	emailAndPassword: {
		enabled: true,
		async sendResetPassword({ user, url }) {
			await sendEmail({
				to: user.email,
				subject: "Reset your password",
				react: reactResetPasswordEmail({
					username: user.name,
					resetLink: url,
				}),
			});
		},
	},
	plugins: [
		organization({
			async sendInvitationEmail(data) {
				await sendEmail({
					to: data.email,
					subject: `You've been invited to join ${data.organization.name}`,
					react: reactInvitationEmail({
						username: data.email,
						invitedByUsername: data.inviter.user.name,
						invitedByEmail: data.inviter.user.email,
						teamName: data.organization.name,
						inviteLink: `${baseURL}/accept-invitation/${data.id}`,
					}),
				});
			},
		}),
		twoFactor({
			otpOptions: {
				async sendOTP({ user, otp }) {
					await sendEmail({
						to: user.email,
						subject: "Your two-factor authentication code",
						react: reactTwoFactorOtpEmail({ username: user.name, otp }),
					});
				},
			},
		}),
		magicLink({
			async sendMagicLink({ email, url }) {
				await sendEmail({
					to: email,
					subject: "Your sign-in link",
					react: reactMagicLinkEmail({ magicLink: url }),
				});
			},
		}),
		passkey(),
		openAPI(),
		bearer(),
		admin(),
		multiSession(),
		nextCookies(),
		stripe({
			stripeClient: new Stripe(process.env.STRIPE_KEY || "sk_test_"),
			stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
			subscription: {
				enabled: true,
				plans: () => {
					const PRO_PRICE_ID = {
						default:
							process.env.STRIPE_PRO_PRICE_ID ??
							"price_1RoxnRHmTADgihIt4y8c0lVE",
						annual:
							process.env.STRIPE_PRO_ANNUAL_PRICE_ID ??
							"price_1RoxnoHmTADgihItzFvVP8KT",
					};
					const PLUS_PRICE_ID = {
						default:
							process.env.STRIPE_PLUS_PRICE_ID ??
							"price_1RoxnJHmTADgihIthZTLmrPn",
						annual:
							process.env.STRIPE_PLUS_ANNUAL_PRICE_ID ??
							"price_1Roxo5HmTADgihItEbJu5llL",
					};

					return [
						{
							name: "Plus",
							priceId: PLUS_PRICE_ID.default,
							annualDiscountPriceId: PLUS_PRICE_ID.annual,
							freeTrial: {
								days: 7,
							},
						},
						{
							name: "Pro",
							priceId: PRO_PRICE_ID.default,
							annualDiscountPriceId: PRO_PRICE_ID.annual,
							freeTrial: {
								days: 7,
							},
						},
					];
				},
			},
		}),
		// SSO : uniquement le provider OIDC de la démo SCIM (portail employé), si activé.
		sso({
			resolveUser: resolveSCIMDemoSSOUser,
			defaultSSO: isSCIMDemoEmployeePortalEnabled()
				? [getSCIMDemoOIDCProvider()]
				: [],
		}),
		...scimDemoPlugins,
		deviceAuthorization({
			expiresIn: "3min",
			interval: "5s",
		}),
		lastLoginMethod(),
		jwt({
			jwt: {
				issuer: baseURL,
			},
		}),
		oauthProvider({
			loginPage: "/sign-in",
			consentPage: "/oauth/consent",
			allowDynamicClientRegistration: true,
			allowUnauthenticatedClientRegistration: true,
			scopes: [
				"openid",
				"profile",
				"email",
				"offline_access",
				"read:organization",
			],
			resources: [baseURL, `${baseURL}/api/mcp`],
			selectAccount: {
				page: "/oauth/select-account",
				shouldRedirect: async ({ headers }) => {
					const allSessions = await getAllDeviceSessions(headers);
					return allSessions?.length >= 1;
				},
			},
			customAccessTokenClaims({ referenceId, scopes }) {
				if (referenceId && scopes.includes("read:organization")) {
					return {
						[`${baseURL}/org`]: referenceId,
					};
				}
				return {};
			},
			postLogin: {
				page: "/oauth/select-organization",
				async shouldRedirect({ session, scopes, headers }) {
					const userOnlyScopes = [
						"openid",
						"profile",
						"email",
						"offline_access",
					];
					if (scopes.every((sc) => userOnlyScopes.includes(sc))) {
						return false;
					}
					// Check if user has multiple organizations to select from
					try {
						const organizations = (await getAllUserOrganizations(
							headers,
						)) as Organization[];
						return (
							organizations.length > 1 ||
							!(
								organizations.length === 1 &&
								organizations.at(0)?.id === session.activeOrganizationId
							)
						);
					} catch {
						return true;
					}
				},
				consentReferenceId({ session, scopes }) {
					if (scopes.includes("read:organization")) {
						const activeOrganizationId = (session?.activeOrganizationId ??
							undefined) as string | undefined;
						if (!activeOrganizationId) {
							throw new APIError("BAD_REQUEST", {
								error: "set_organization",
								error_description: "must set organization for these scopes",
							});
						}
						return activeOrganizationId;
					} else {
						return undefined;
					}
				},
			},
		}),
	],
} satisfies BetterAuthOptions;

export const auth = betterAuth({
	...authOptions,
	plugins: [
		...(authOptions.plugins ?? []),
		customSession(
			async ({ user, session }) => {
				return {
					user: {
						...user,
						customField: "customField",
					},
					session,
				};
			},
			authOptions,
			{ shouldMutateListDeviceSessionsEndpoint: true },
		),
	],
});

export type Session = typeof auth.$Infer.Session;
export type ActiveOrganization = typeof auth.$Infer.ActiveOrganization;
export type OrganizationRole = ActiveOrganization["members"][number]["role"];
export type Invitation = typeof auth.$Infer.Invitation;
export type DeviceSession = Awaited<
	ReturnType<typeof auth.api.listDeviceSessions>
>[number];

async function getAllDeviceSessions(headers: Headers): Promise<unknown[]> {
	return await auth.api.listDeviceSessions({
		headers,
	});
}

async function getAllUserOrganizations(headers: Headers): Promise<unknown[]> {
	return await auth.api.listOrganizations({
		headers,
	});
}
