import { Controller, Get, Middleware, Post } from "@overnightjs/core";
import { IdentitySchemas, UtilitySchemas } from "@plunk/shared";
import type { Request, Response } from "express";
import signale from "signale";
import { prisma } from "../database/prisma";
import { NotFound } from "../exceptions";
import { type IJwt, isAuthenticated } from "../middleware/auth";
import { ProjectService } from "../services/ProjectService";
import { Keys } from "../services/keys";
import { redis } from "../services/redis";
import { getIdentities, getIdentityVerificationAttributes, ses, verifyIdentity } from "../util/ses";

@Controller("identities")
export class Identities {
	@Get("id/:id")
	@Middleware([isAuthenticated])
	public async getVerification(req: Request, res: Response) {
		const { id } = UtilitySchemas.id.parse(req.params);

		const project = await ProjectService.id(id);

		if (!project) {
			throw new NotFound("project");
		}

		if (!project.email) {
			return res.status(200).json({ success: false });
		}

		const attributes = await getIdentityVerificationAttributes(project.email);

		if (attributes.status === "Success" && !project.verified) {
			await prisma.project.update({ where: { id }, data: { verified: true } });

			await redis.del(Keys.Project.id(project.id));
			await redis.del(Keys.Project.secret(project.secret));
			await redis.del(Keys.Project.public(project.public));
		}

		return res.status(200).json({ tokens: attributes.tokens });
	}

	@Middleware([isAuthenticated])
	@Post("create")
	public async addIdentity(req: Request, res: Response) {
		const { id, email } = IdentitySchemas.create.parse(req.body);

		const { userId } = res.locals.auth as IJwt;

		const project = await ProjectService.id(id);

		if (!project) {
			throw new NotFound("project");
		}

		const existingProject = await prisma.project.findFirst({
			where: { email: { endsWith: email.split("@")[1] } },
		});

		if (existingProject) {
			throw new Error("Domain already attached to another project");
		}

		const tokens = await verifyIdentity(email);

		await prisma.project.update({
			where: { id },
			data: { email, verified: false },
		});

		await redis.del(Keys.User.projects(userId));
		await redis.del(Keys.Project.id(project.id));

		return res.status(200).json({ success: true, tokens });
	}

	@Middleware([isAuthenticated])
	@Post("reset")
	public async resetIdentity(req: Request, res: Response) {
		const { id } = UtilitySchemas.id.parse(req.body);

		const { userId } = res.locals.auth as IJwt;

		const project = await ProjectService.id(id);

		if (!project) {
			throw new NotFound("project");
		}

		await prisma.project.update({
			where: { id },
			data: { email: null, verified: false },
		});

		await redis.del(Keys.User.projects(userId));
		await redis.del(Keys.Project.id(project.id));

		return res.status(200).json({ success: true });
	}

	@Post("update")
	public async updateIdentities(req: Request, res: Response) {
		const count = await prisma.project.count({
			where: { email: { not: null } },
		});

		for (let i = 0; i < count; i += 99) {
			const dbIdentities = await prisma.project.findMany({
				where: { email: { not: null } },
				select: { id: true, email: true },
				skip: i,
				take: 99,
			});

			const awsIdentities = await getIdentities(dbIdentities.map((i) => i.email as string));

			for (const identity of awsIdentities) {
				const projectId = dbIdentities.find((i) => i.email?.endsWith(identity.email));

				const project = await ProjectService.id(projectId?.id as string);

				if (identity.status === "Failed") {
					signale.info(`Restarting verification for ${identity.email}`);
					try {
						void verifyIdentity(identity.email);
					} catch (e) {
						// @ts-ignore
						if (e.Code === "Throttling") {
							signale.warn("Throttling detected, waiting 5 seconds");
							await new Promise((r) => setTimeout(r, 5000));
						}
					}
				}

				await prisma.project.update({
					where: { id: projectId?.id as string },
					data: { verified: identity.status === "Success" },
				});

				if (project && !project.verified && identity.status === "Success") {
					signale.success(`Successfully verified ${identity.email}`);
					void ses.setIdentityFeedbackForwardingEnabled({
						Identity: identity.email,
						ForwardingEnabled: false,
					});

					await redis.del(Keys.Project.id(project.id));
					await redis.del(Keys.Project.secret(project.secret));
					await redis.del(Keys.Project.public(project.public));
				}

				if (project?.verified && identity.status !== "Success") {
					await redis.del(Keys.Project.id(project.id));
					await redis.del(Keys.Project.secret(project.secret));
					await redis.del(Keys.Project.public(project.public));
				}
			}
		}

		return res.status(200).json({ success: true });
	}
}
