import { logger } from "../../env";
import type { BetterAuthOptions } from "../../types";
import { generateId as defaultGenerateId } from "../../utils/id";
import type { BetterAuthDBSchema, DBFieldAttribute } from "../type";
import { initGetDefaultModelName } from "./get-default-model-name";

export const initGetIdField = ({
	usePlural,
	schema,
	disableIdGeneration,
	options,
	customIdGenerator,
	supportsUUIDs,
}: {
	usePlural?: boolean;
	schema: BetterAuthDBSchema;
	options: BetterAuthOptions;
	disableIdGeneration?: boolean;
	customIdGenerator?: ((props: { model: string }) => string) | undefined;
	supportsUUIDs?: boolean;
}) => {
	const getDefaultModelName = initGetDefaultModelName({
		usePlural: usePlural,
		schema,
	});

	const idField = ({
		customModelName,
		forceAllowId,
	}: {
		customModelName?: string;
		forceAllowId?: boolean;
	}) => {
		const useNumberId = options.advanced?.database?.generateId === "serial";
		const useUUIDs = options.advanced?.database?.generateId === "uuid";

		const shouldGenerateId: boolean = (() => {
			if (disableIdGeneration) {
				return false;
			} else if (useNumberId && !forceAllowId) {
				// if force allow is true, then we should be using their custom provided id.
				return false;
			} else if (useUUIDs) {
				// should only generate UUIDs via JS if the database doesn't support natively generating UUIDs.
				return !supportsUUIDs;
			} else {
				return true;
			}
		})();

		const model = getDefaultModelName(customModelName ?? "id");
		return {
			type: useNumberId ? "number" : "string",
			required: shouldGenerateId ? true : false,
			...(shouldGenerateId
				? {
						defaultValue() {
							if (disableIdGeneration) return undefined;
							const generateId = options.advanced?.database?.generateId;

							// let the database handle id generation
							if (generateId === false || generateId === "serial")
								return undefined;

							// user-provided function takes highest priority
							if (typeof generateId === "function") {
								return generateId({
									model,
								});
							}

							// user-provided "uuid" option
							if (generateId === "uuid") {
								return crypto.randomUUID();
							}

							// database adapter-level custom id generator
							if (customIdGenerator) {
								return customIdGenerator({ model });
							}

							// fallback to default id generation
							return defaultGenerateId();
						},
					}
				: {}),
			transform: {
				input: (value) => {
					// Uncomment if need to debug id transformation
					// console.log(`transforming id: `, {
					// 	id: value,
					// 	...(useNumberId ? { useNumberId } : {}),
					// 	...(useUUIDs ? { useUUIDs } : {}),
					// 	...(forceAllowId ? { forceAllowId } : {}),
					// });
					if (!value) return undefined;

					if (useNumberId) {
						const numberValue = Number(value);
						// if invalid number, fallback to DB generated number id.
						if (isNaN(numberValue)) {
							return undefined;
						}
						return numberValue;
					}

					if (useUUIDs) {
						// if it's generated by us, then we should return the value as is.
						if (shouldGenerateId && !forceAllowId) return value;
						if (disableIdGeneration) return undefined;
						// if DB will handle UUID generation, then we should return undefined.
						if (supportsUUIDs) return undefined;
						// if forceAllowId is true, it means we should be using the ID provided during the adapter call.
						if (forceAllowId && typeof value === "string") {
							const uuidRegex =
								/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
							if (uuidRegex.test(value)) {
								return value;
							} else {
								const err = new Error();
								const stack = err.stack
									?.split("\n")
									.filter((_, i) => i !== 1)
									.join("\n")
									.replace("Error:", "");
								logger.warn(
									"[Adapter Factory] - Invalid UUID value for field `id` provided when `forceAllowId` is true. Generating a new UUID.",
									stack,
								);
							}
						}
						// if the value is not a string, and the database doesn't support generating it's own UUIDs, then we should be generating the UUID.
						if (typeof value !== "string" && !supportsUUIDs) {
							return crypto.randomUUID();
						}
						return undefined;
					}

					return value;
				},
				output: (value) => {
					if (!value) return undefined;
					return String(value);
				},
			},
		} satisfies DBFieldAttribute;
	};

	return idField;
};
