// @ts-check

const {z} = require("zod")
const {randomUUID} = require("crypto")
const {defineEventHandler, getValidatedQuery, sendRedirect, createError, H3Event} = require("h3")
const {SnowTransfer, tokenless} = require("snowtransfer")
const DiscordTypes = require("discord-api-types/v10")
const getRelativePath = require("get-relative-path")

const {as, db, sync} = require("../../passthrough")
const {id, permissions} = require("../../../addbot")
/** @type {import("../auth")} */
const auth = sync.require("../auth")
const {reg} = require("../../matrix/read-registration")

const redirect_uri = `${reg.ooye.bridge_origin}/oauth`

const schema = {
	first: z.object({
		action: z.string().optional()
	}),
	code: z.object({
		state: z.string(),
		code: z.string(),
		guild_id: z.string().optional()
	}),
	token: z.object({
		token_type: z.string(),
		access_token: z.string(),
		expires_in: z.coerce.number(),
		refresh_token: z.string(),
		scope: z.string()
	})
}

/**
 * @param {H3Event} event
 * @returns {(string) => {user: {getGuilds: () => Promise<DiscordTypes.RESTGetAPICurrentUserGuildsResult>}}}
 */
function getClient(event) {
	/* c8 ignore next */
	return event.context.getClient || (accessToken => new SnowTransfer(`Bearer ${accessToken}`))
}

/**
 * @param {H3Event} event
 * @returns {typeof tokenless.getOauth2Token}
 */
function getOauth2Token(event) {
	/* c8 ignore next */
	return event.context.getOauth2Token || tokenless.getOauth2Token
}

as.router.get("/oauth", defineEventHandler(async event => {
	const session = await auth.useSession(event)
	let scope = "guilds"

	if (!reg.ooye.web_password || reg.ooye.web_password === session.data.password) {
		const parsedFirstQuery = await getValidatedQuery(event, schema.first.safeParse)
		if (parsedFirstQuery.data?.action === "add") {
			scope = "bot+guilds"
			await session.update({selfService: false})
		} else if (parsedFirstQuery.data?.action === "add-self-service") {
			scope = "bot+guilds"
			await session.update({selfService: true})
		}
	}

	async function tryAgain() {
		const newState = randomUUID()
		await session.update({state: newState})
		return sendRedirect(event, `https://discord.com/oauth2/authorize?client_id=${id}&scope=${scope}&permissions=${permissions}&response_type=code&redirect_uri=${redirect_uri}&state=${newState}`)
	}

	const parsedQuery = await getValidatedQuery(event, schema.code.safeParse)
	if (!parsedQuery.success) return tryAgain()

	const savedState = session.data.state
	if (!savedState) throw createError({status: 400, message: "Missing state", data: "Missing saved state parameter. Please try again, and make sure you have cookies enabled."})
	if (savedState != parsedQuery.data.state) return tryAgain()

	const oauthResult = await getOauth2Token(event)(id, redirect_uri, reg.ooye.discord_client_secret, parsedQuery.data.code)
	const parsedToken = schema.token.parse(oauthResult)

	const userID = Buffer.from(parsedToken.access_token.split(".")[0], "base64").toString()
	const client = getClient(event)(parsedToken.access_token)

	const guilds = await client.user.getGuilds()
	var managedGuilds = guilds.filter(g => BigInt(g.permissions) & DiscordTypes.PermissionFlagsBits.ManageGuild).map(g => g.id)
	await session.update({managedGuilds, userID, state: undefined})

	// Set auto-create for the guild
	// @ts-ignore
	if (managedGuilds.includes(parsedQuery.data.guild_id)) {
		const autocreateInteger = +!session.data.selfService
		db.prepare("INSERT INTO guild_active (guild_id, autocreate) VALUES (?, ?) ON CONFLICT DO UPDATE SET autocreate = ?").run(parsedQuery.data.guild_id, autocreateInteger, autocreateInteger)
	}

	if (parsedQuery.data.guild_id) {
		return sendRedirect(event, getRelativePath(event.path, `/guild?guild_id=${parsedQuery.data.guild_id}`), 302)
	}

	return sendRedirect(event, getRelativePath(event.path, "/"), 302)
}))
