import { TRPCError } from "@trpc/server";
import { deleteCookie, setCookie } from "cookies-next";
import { add } from "date-fns";
import type { NextApiRequest, NextApiResponse } from "next";
import type { UsersId } from "~/schemas/public/Users";
import type { Context } from "~/server/api/trpc";
import { signToken } from "~/server/auth/jwt";
import { db } from "~/server/db";
import { isBanned } from "~/server/services/banlist.service";
import {
	comparePasswords,
	generateSalt,
	hashPassword,
} from "~/server/services/password.service";
import {
	addUser,
	findUser_byEmail,
	findUser_byId,
	findUser_byId_MINI,
} from "~/server/services/user.service";
import { checkCtx } from "~/server/utils/ctxChecker";
import type { Result } from "~/utils/result";
import { TOKEN_CONFIG } from "../auth/configs";

export const createUserAdmin = async ({
	email,
	password,
	nome,
	cognome,
	telefono,
}: {
	email: string;
	password: string;
	nome: string;
	cognome: string;
	telefono: string;
}) => {
	const banned = await isBanned({ data: { email }, db });
	if (banned) {
		throw new TRPCError({
			code: "FORBIDDEN",
			message: "Utente bannato",
		});
	}

	const salt = generateSalt();
	const hashedPassword = await hashPassword(password, salt);

	const user = await addUser({
		data: {
			cognome: cognome.trim(),
			email,
			isAdminMade: true,
			nome: nome.trim(),
			password: hashedPassword,
			salt,
			telefono,
			username: `${cognome.trim()} ${nome.trim()}`,
		},
		db,
	});
	if (!user) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: "Errore in fase di registrazione",
		});
	}

	return { status: "success" as const };
};

type LoginErrors =
	| { _tag: "NotFound"; message: string }
	| { _tag: "Fail"; message: string };

// FLOWS: login | 2 | Login function
export const logIn = async ({
	email,
	password,
	ctx: { req, res },
}: {
	email: string;
	password: string;
	ctx: Context;
}): Promise<Result<null, LoginErrors>> => {
	try {
		checkCtx({ req, res });

		// Get the user from the collection
		const user = await findUser_byEmail({ email });

		if (!user) {
			return {
				ok: false,
				error: {
					_tag: "NotFound",
					message:
						"Questo utente non esiste. Devi avere un account per effettuare il login.",
				},
			};
		}
		//Check if user is blocked or unverified

		if (user.isBlocked) {
			return {
				ok: false,
				error: {
					_tag: "Fail",
					message: "Errore in Login, non è possibile effettuare il login.",
				},
			};
		}

		// If Admin and password is "changeme", skip password check
		const skipPwCheck = user.isAdmin && user.password === "changeme";

		if (!skipPwCheck) {
			const passworIsMatch = await comparePasswords({
				hashedPassword: user.password,
				password,
				salt: user.salt,
			});

			if (!passworIsMatch) {
				return {
					ok: false,
					error: {
						_tag: "Fail",
						message: "Password errata, riprova.",
					},
				};
			}
		}

		const payload = {
			email: user.email,
			id: user.id,
			isAdmin: user.isAdmin,
			isBlocked: user.isBlocked,
			nome: user.nome,
			username: user.username,
			mustChangePassword: user.mustChangePassword,
		};

		const accessToken = await signToken(
			payload,
			TOKEN_CONFIG.ACCESS_TOKEN_EXPIRY,
			"accessToken",
		);
		const refreshToken = await signToken(
			payload,
			TOKEN_CONFIG.REFRESH_TOKEN_EXPIRY,
			"refreshToken",
		);

		await setCookie(TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_NAME, accessToken, {
			...TOKEN_CONFIG.COOKIE_OPTIONS,
			expires: add(new Date(), {
				hours: TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_EXPIRY,
			}),
			req,
			res,
		});
		await setCookie(TOKEN_CONFIG.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
			...TOKEN_CONFIG.COOKIE_OPTIONS,
			expires: add(new Date(), {
				days: TOKEN_CONFIG.REFRESH_TOKEN_COOKIE_EXPIRY,
			}),
			req,
			res,
		});

		return { ok: true, value: null };
	} catch (e) {
		console.error("Login error:", e);
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore durante il login: ${(e as Error).message}`,
		});
	}
};

export const swapAuth = async ({
	ctx: { req, res },
	userId,
}: {
	ctx: Context;
	userId: UsersId;
}) => {
	checkCtx({ req, res });
	try {
		const user = await findUser_byId_MINI({ userId });
		if (!user) {
			throw new TRPCError({
				code: "NOT_FOUND",
				message: `Swap: Utente non trovato: ${userId}`,
			});
		}

		const accessToken = await signToken(
			user,
			TOKEN_CONFIG.ACCESS_TOKEN_EXPIRY,
			"accessToken",
		);
		const refreshToken = await signToken(
			user,
			TOKEN_CONFIG.REFRESH_TOKEN_EXPIRY,
			"refreshToken",
		);

		// Send Access Token in Cookie
		await setCookie(TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_NAME, accessToken, {
			...TOKEN_CONFIG.COOKIE_OPTIONS,
			expires: add(new Date(), {
				hours: TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_EXPIRY,
			}),
			req,
			res,
		});
		await setCookie(TOKEN_CONFIG.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
			...TOKEN_CONFIG.COOKIE_OPTIONS,
			expires: add(new Date(), {
				days: TOKEN_CONFIG.REFRESH_TOKEN_COOKIE_EXPIRY,
			}),
			req,
			res,
		});
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore Swap Auth: ${(e as Error).message}`,
		});
	}
};

export const logOut = async ({
	req,
	res,
}: {
	req?: NextApiRequest;
	res?: NextApiResponse;
}) => {
	checkCtx({ req, res });
	try {
		await deleteCookie(TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_NAME, { req, res });
		await deleteCookie(TOKEN_CONFIG.REFRESH_TOKEN_COOKIE_NAME, { req, res });
		return { status: "success" };
	} catch (err: unknown) {
		console.error(err);
		throw err;
	}
};

export const passwordChange = async ({
	userId,
	oldpassword,
	newpassword,
	ctx,
}: {
	userId: UsersId;
	oldpassword: string;
	newpassword: string;
	ctx: Context;
}) => {
	const { req, res } = ctx;
	if (!req || !res) {
		throw new TRPCError({
			code: "BAD_REQUEST",
			message: "Request/Response context mancante",
		});
	}
	const dbUser = await findUser_byId({ id: userId });
	if (!dbUser) {
		throw new TRPCError({
			code: "NOT_FOUND",
			message: `Password Change: Utente non trovato: ${userId}`,
		});
	}
	const banned = await isBanned({ data: { email: dbUser.email }, db });
	if (banned) {
		throw new TRPCError({
			code: "FORBIDDEN",
			message: "Utente bannato",
		});
	}

	const isPasswordCorrect = await comparePasswords({
		hashedPassword: dbUser.password,
		password: oldpassword,
		salt: dbUser.salt,
	});
	if (!isPasswordCorrect) {
		throw new TRPCError({
			code: "BAD_REQUEST",
			message: `Password Change: Password errata per utente: ${userId}`,
		});
	}

	const salt = generateSalt();
	const hashedPassword = await hashPassword(newpassword, salt);
	await db
		.updateTable("users")
		.set({
			password: hashedPassword,
			salt,
			mustChangePassword: false,
		})
		.where("id", "=", userId)
		.execute();

	// Refresh tokens with updated mustChangePassword value
	const payload = {
		email: dbUser.email,
		id: dbUser.id,
		isAdmin: dbUser.isAdmin,
		isBlocked: dbUser.isBlocked,
		nome: dbUser.nome,
		username: dbUser.username,
		mustChangePassword: false,
	};

	const accessToken = await signToken(
		payload,
		TOKEN_CONFIG.ACCESS_TOKEN_EXPIRY,
		"accessToken",
	);
	const refreshToken = await signToken(
		payload,
		TOKEN_CONFIG.REFRESH_TOKEN_EXPIRY,
		"refreshToken",
	);

	await setCookie(TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_NAME, accessToken, {
		...TOKEN_CONFIG.COOKIE_OPTIONS,
		expires: add(new Date(), {
			hours: TOKEN_CONFIG.ACCESS_TOKEN_COOKIE_EXPIRY,
		}),
		req,
		res,
	});
	await setCookie(TOKEN_CONFIG.REFRESH_TOKEN_COOKIE_NAME, refreshToken, {
		...TOKEN_CONFIG.COOKIE_OPTIONS,
		expires: add(new Date(), {
			days: TOKEN_CONFIG.REFRESH_TOKEN_COOKIE_EXPIRY,
		}),
		req,
		res,
	});

	return {
		status: "success" as const,
	};
};
