import crypto from "node:crypto";
import { TRPCError } from "@trpc/server";
import type { UserInvites } from "~/schemas/public/UserInvites";
import type { UsersId } from "~/schemas/public/Users";
import type { Context } from "~/server/api/trpc";
import { swapAuth } from "~/server/controllers/auth.controller";
import { editAccountHandler } from "~/server/controllers/user.controller";
import { db } from "~/server/db";
import { generateSalt, hashPassword } from "~/server/services/password.service";
import { findUser_byEmail } from "~/server/services/user.service";
import type { Result } from "~/utils/result";
import { NewMail } from "../services/mailer";

const INVITE_EXPIRY_HOURS = 48;

export async function createInviteToken(email: string) {
	// Generate a secure random token
	const token = crypto.randomBytes(32).toString("hex");
	const expiresAt = new Date(Date.now() + INVITE_EXPIRY_HOURS * 60 * 60 * 1000);

	try {
		// Store in database
		await db
			.insertInto("user_invites")
			.values({
				email,
				token,
				expires_at: expiresAt,
			})
			.execute();
	} catch (e) {
		console.error("Error creating invite token:", e);
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore durante la creazione del token di invito.: ${(e as Error).message}`,
		});
	}
	return token;
}
export type InviteErrors =
	| { _tag: "AlreadyUser" }
	| { _tag: "InternalError"; message: string }
	| TokenVerErrors;

export const InviteVerification = async (
	token: string,
	email: string | null,
): Promise<Result<string, InviteErrors>> => {
	try {
		if (email) {
			const existingUser = await findUser_byEmail({ email });
			if (existingUser?.usedInvite) {
				return { ok: false, error: { _tag: "AlreadyUser" } };
			}
		}

		const ver = await verifyInviteToken(token);

		if (!ver.ok) {
			return {
				ok: false,
				error: ver.error,
			};
		}

		return { ok: true, value: ver.value.email };
	} catch (e) {
		console.error("Error verifying invite token:", e);
		return {
			ok: false,
			error: {
				_tag: "InternalError",
				message: `Errore durante la verifica del token di invito.: ${(e as Error).message}`,
			},
		};
	}
};
type InviteAcceptanceErrors =
	| {
			_tag: "UserNotFound";
			message: string;
	  }
	| TokenVerErrors;
export const InviteAcceptance = async (
	ctx: Context,
	token: string,
	email: string,
	password: string,
): Promise<Result<UserInvites, InviteAcceptanceErrors>> => {
	try {
		const user = await findUser_byEmail({ email });
		if (!user) {
			return {
				ok: false,
				error: {
					_tag: "UserNotFound",
					message: `Accept Invite: Utente non trovato con email: ${email}`,
				},
			};
		}

		const ver = await verifyInviteToken(token);

		if (!ver.ok) {
			return {
				ok: false,
				error: ver.error,
			};
		}

		// Create user with password
		const salt = generateSalt();
		const hashedPassword = await hashPassword(password, salt);

		await editAccountHandler({
			id: user.id,
			data: {
				password: hashedPassword,
				salt,
				usedInvite: true,
			},
		});

		// Mark invite as used
		await consumeInviteToken(token);
		await swapAuth({ ctx, userId: user.id });

		return { ok: true, value: ver.value };
	} catch (e) {
		console.error("Error verifying invite token:", e);
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore durante l'accettazione del token di invito.: ${(e as Error).message}`,
		});
	}
};

export type TokenVerErrors = { _tag: "TokenNotFound" } | { _tag: "Expired" };

export async function verifyInviteToken(
	token: string,
): Promise<Result<UserInvites, TokenVerErrors>> {
	try {
		const invite = await db
			.selectFrom("user_invites")
			.selectAll("user_invites")
			.where("token", "=", token)
			//.where("expires_at", ">", new Date())
			.executeTakeFirst();
		if (!invite) {
			return { ok: false, error: { _tag: "TokenNotFound" } };
		}
		if (invite.expires_at < new Date()) {
			return { ok: false, error: { _tag: "Expired" } };
		}

		return { ok: true, value: invite };
	} catch (e) {
		console.error("Error verifying invite token:", e);
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore durante la verifica del token di invito.: ${(e as Error).message}`,
		});
	}
}

export async function consumeInviteToken(token: string) {
	try {
		await db.deleteFrom("user_invites").where("token", "=", token).execute();
	} catch (e) {
		console.error("Error consuming invite token:", e);
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore durante il consumo del token di invito.: ${(e as Error).message}`,
		});
	}
}

export const sendInvitoEmail = async ({
	userId,
	email,
	//password,
	nome,
	inviteUrl,
}: {
	userId: UsersId;
	email: string;
	//password: string;
	nome: string;
	inviteUrl: string;
}) => {
	try {
		await NewMail({
			template: {
				mailType: "invito",
				props: {
					//email,
					nome,
					//password,
					inviteUrl,
				},
			},
			mail: {
				subject: "Procedi ora su Infoalloggi.it",
				to: email,
			},
			userId,
		});

		return true;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error sending email: ${(e as Error).message}`,
		});
	}
};
