import { z } from "zod";
import moment from "moment";
import { prisma } from "#lib/server/prisma.js";
import { encodeToken } from "../../../lib/auth/token.js";
import { APIError, defineApiRoute } from "astro-typesafe-api/server";
import { TokenType } from "#lib/auth/types.js";

export const GET = defineApiRoute({
	meta: {
		description:
			"Erstellt, basierend auf einem existierenden und gültigen Refresh Tokens, einen neuen Access Token, welcher zur Authentifizierung genutzt werden kann. Der resultierende Access Token ist nur 2 Tage gültig und muss danach neu generiert werden. Diese Funktion gibt ebenfalls einen neuen Refresh Token zurück, der alte wird dadurch invalidiert.",
		tags: ["Benutzer"],
		summary: "Access Token anfragen.",
	},
	input: z.object({
		refreshToken: z.string(),
	}),
	output: z.object({
		accessToken: z.string(),
		accessTokenExpiry: z.number(),
		refreshToken: z.string(),
		refreshTokenExpiry: z.number(),
	}),

	async fetch(input, ctx) {
		/**
		 * Wir benutzen rolling refresh tokens, also löschen wir den alten Token und stellen einen neuen aus,
		 * damit dieser nicht geklaut werden kann.
		 */
		const response = await prisma.refreshTokens.findUnique({
			where: {
				token: input.refreshToken,
			},
		});

		if (!response) {
			throw new APIError({
				code: "BAD_REQUEST",
				message: "Der gegebene refresh token ist nicht gültig.",
			});
		}

		if (response.expiry < new Date()) {
			// Falls der Token abgelaufen ist, müssen wir ihn löschen.
			await prisma.refreshTokens.delete({
				where: {
					id: response.id,
				},
			});
			throw new APIError({
				code: "BAD_REQUEST",
				message: "Der gegebene refresh token ist nicht gültig.",
			});
		}

		// TODO: Das können wir später implementieren, falls wir das wirklich brauchen.
		// if (response.ip !== opts.ctx.ip) {
		// 	// Falls der Token nicht von der selben IP Adresse kommt, müssen wir ihn löschen.
		// 	await prisma.refreshTokens.delete({
		// 		where: {
		// 			id: response.id
		// 		}
		// 	})
		// 	throw new APIError({ code: "BAD_REQUEST", message: "Der gegebene refresh token wurde von einer anderen IP-Adresse ausgestellt, aus Sicherheitsgründen haben wir uns entschieden diesen zu invalidieren." });
		// }

		const user = await prisma.benutzer.findUnique({
			where: {
				id: response.benutzer_id,
			},
		});

		if (!user) {
			// Falls der Nutzer nicht mehr existiert müssen wir den Refresh Token invalidieren.
			await prisma.refreshTokens.delete({
				where: {
					id: response.id,
				},
			});
			throw new APIError({
				code: "BAD_REQUEST",
				message: "Der gegebene refresh token ist nicht mehr gültig.",
			});
		}

		// Wir löschen den alten Token.
		await prisma.refreshTokens.delete({
			where: {
				id: response.id,
			},
		});

		const refreshTokenExpiry = moment().add(30, "days");
		const refreshToken = encodeToken({
			uid: user.uid,
			typ: TokenType.Refresh,
			exp: refreshTokenExpiry.valueOf(),
		});

		// Und erstellen einen neuen
		await prisma.refreshTokens.create({
			data: {
				benutzer_id: user.id,
				expiry: refreshTokenExpiry.toDate(),
				ip: "",
				token: refreshToken,
			},
		});

		const accessTokenExpiry = moment().add(2, "days").valueOf();
		const accessToken = encodeToken({
			uid: user.uid,
			typ: TokenType.Access,
			exp: accessTokenExpiry,
		});

		return {
			accessToken,
			accessTokenExpiry: accessTokenExpiry,
			refreshToken,
			refreshTokenExpiry: refreshTokenExpiry.valueOf(),
		};
	},
});
