import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import {
	admin as adminPlugin,
	genericOAuth,
	// apiKey,
	// createAuthMiddleware,
	//customSession,
	jwt,
	lastLoginMethod,
	username,
} from "better-auth/plugins";
//import { eq } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import * as rawSchema from "../db/schema/auth.schema.js";
import {
	ac,
	admin,
	manager,
	systemAdmin,
	transport,
	user,
} from "./auth.permissions.js";
import { allowedOrigins } from "./cors.utils.js";
import { sendEmail } from "./sendEmail.utils.js";

function decodeJwtPayload<T = Record<string, unknown>>(jwt: string): T {
	const parts = jwt.split(".");
	if (parts.length < 2) {
		throw new Error("Invalid JWT");
	}

	const payload = parts[1]?.replace(/-/g, "+").replace(/_/g, "/");

	const padded = payload?.padEnd(
		payload.length + ((4 - (payload.length % 4)) % 4),
		"=",
	);

	const json = Buffer.from(padded ?? "", "base64").toString("utf8");
	return JSON.parse(json) as T;
}

function normalizeGroups(groups?: unknown): string[] {
	if (!Array.isArray(groups)) return [];

	return groups
		.filter((g): g is string => typeof g === "string")
		.map((g) => g.trim().toLowerCase())
		.filter((g) => g.length > 0);
}

type VoidAuthClaims = {
	sub: string;
	name?: string;
	preferred_username?: string;
	email?: string;
	email_verified?: boolean;
	groups?: string[];
	picture?: string;
	iss?: string;
	aud?: string;
	exp?: number;
	iat?: number;
};

export const schema = {
	user: rawSchema.user,
	session: rawSchema.session,
	account: rawSchema.account,
	verification: rawSchema.verification,
	jwks: rawSchema.jwks,
	apiKey: rawSchema.apikey, // 🔑 rename to apiKey
};

const hasOAuth =
	Boolean(process.env.PROVIDER) &&
	Boolean(process.env.CLIENT_ID) &&
	Boolean(process.env.CLIENT_SECRET) &&
	Boolean(process.env.DISCOVERY_URL);

if (!hasOAuth) {
	console.warn("Missing oauth data.");
}

const oauthPlugins = hasOAuth
	? [
			genericOAuth({
				config: [
					{
						providerId: process.env.PROVIDER!,
						clientId: process.env.CLIENT_ID!,
						clientSecret: process.env.CLIENT_SECRET!,
						discoveryUrl: process.env.DISCOVERY_URL!,
						scopes: (process.env.CLIENT_SCOPES ?? "")
							.split(/[,\s]+/)
							.filter(Boolean),
						pkce: true,
						requireIssuerValidation: true,
						redirectURI: `${process.env.URL}/lst/api/auth/oauth2/callback/${process.env.PROVIDER!}`,
						getUserInfo: async (tokens) => {
							if (!tokens.idToken) {
								throw new Error("VoidAuth did not return an idToken");
							}

							const claims = decodeJwtPayload<VoidAuthClaims>(tokens.idToken);
							const groups = normalizeGroups(claims.groups);

							return {
								id: claims.sub,
								email: claims.email ?? "",
								name:
									claims.name ??
									claims.preferred_username ??
									claims.email ??
									"Unknown User",
								image: claims.picture ?? null,
								emailVerified: Boolean(claims.email_verified),
								groups,
								username: claims.preferred_username ?? null,
							} as any;
						},

						mapProfileToUser: async (profile) => {
							return {
								name: profile.name,
								role: profile.groups?.includes("lst_admins")
									? "systemAdmin"
									: profile.groups?.includes("admins")
										? "admin"
										: "user",
							};
						},
					},
				],
			}),
		]
	: [];

export const auth = betterAuth({
	appName: "lst",
	baseURL: `${process.env.URL}/lst/api/auth`,
	database: drizzleAdapter(db, {
		provider: "pg",
		schema,
	}),
	trustedOrigins: allowedOrigins,
	user: {
		additionalFields: {
			role: {
				type: "string",
				//required: false,
				input: false,
			},
		},
	},
	account: {
		encryptOAuthTokens: true,
		updateAccountOnSignIn: true,
		accountLinking: {
			enabled: true,
			trustedProviders: ["voidauth"],
		},
	},
	plugins: [
		jwt({ jwt: { expirationTime: "1h" } }),
		//apiKey(),
		adminPlugin({
			ac,
			roles: {
				admin,
				user,
				manager,
				transport,
				systemAdmin,
			},
		}),
		lastLoginMethod(),
		username({
			minUsernameLength: 5,
			usernameValidator: (username) => {
				if (username === "admin" || username === "root") {
					return false;
				}
				return true;
			},
		}),
		...oauthPlugins,

		// customSession(async ({ user, session }) => {
		// 	const roles = await db
		// 		.select({ roles: rawSchema.user.role })
		// 		.from(rawSchema.user)
		// 		.where(eq(rawSchema.user.id, session.id));
		// 	return {
		// 		roles,
		// 		user: {
		// 			...user,
		// 			//newField: "newField",
		// 		},
		// 		session,
		// 	};
		// }),
	],

	emailAndPassword: {
		enabled: true,
		minPasswordLength: 8, // optional config
		resetPasswordTokenExpirySeconds: process.env.RESET_EXPIRY_SECONDS, // time in seconds
		sendResetPassword: async ({ user, token }) => {
			const frontendUrl = `${process.env.URL}/lst/app/user/resetpassword?token=${token}`;
			const expiryMinutes = Math.floor(
				parseInt(process.env.RESET_EXPIRY_SECONDS ?? "3600", 10) / 60,
			);
			const expiryText =
				expiryMinutes >= 60
					? `${expiryMinutes / 60} hour${expiryMinutes === 60 ? "" : "s"}`
					: `${expiryMinutes} minutes`;
			const emailData = {
				email: user.email,
				subject: "LST- Forgot password request",
				template: "forgotPassword",
				context: {
					username: user.name,
					email: user.email,
					url: frontendUrl,
					expiry: expiryText,
				},
			};
			await sendEmail(emailData);
		},
		// onPasswordReset: async ({ user }, request) => {
		//     // your logic here
		//     console.log(`Password for user ${user.email} has been reset.`);
		// },
	},
	session: {
		expiresIn: 60 * 60,
		updateAge: 60 * 5,
		freshAge: 60 * 2,
		cookieCache: {
			enabled: true,
			maxAge: 5 * 60,
		},
	},
	cookie: {
		path: "/lst",
		sameSite: "lax",
		secure: false,
		httpOnly: true,
	},
	// hooks: {
	// 	after: createAuthMiddleware(async (ctx) => {
	// 		if (ctx.path.startsWith("/login")) {
	// 			const newSession = ctx.context.newSession;
	// 			if (newSession) {
	// 				// something here later
	// 			}
	// 		}
	// 	}),
	// },
	events: {
		// async onSignInSuccess({ user }: { user: User }) {
		// 	await db
		// 		.update(rawSchema.user)
		// 		.set({ lastLogin: new Date() })
		// 		.where(eq(schema.user.id, user.id));
		// },
	},
});
