/*
	Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
	Copyright (C) 2023 Spacebar and Spacebar Contributors
	
	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Affero General Public License as published
	by the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Affero General Public License for more details.
	
	You should have received a copy of the GNU Affero General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

import { route } from "@spacebar/api";
import { Config, DiscordApiErrors, emitEvent, GuildRoleCreateEvent, GuildRoleUpdateEvent, Member, Role, Snowflake } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { Not } from "typeorm";
import { RoleModifySchema, RolePositionUpdateSchema } from "@spacebar/schemas";

const router: Router = Router({ mergeParams: true });

router.get("/", route({}), async (req: Request, res: Response) => {
    const guild_id = req.params.guild_id as string;

    await Member.IsInGuildOrFail(req.user_id, guild_id);

    const roles = await Role.find({ where: { guild_id: guild_id } });

    return res.json(roles);
});

router.post(
    "/",
    route({
        requestBody: "RoleModifySchema",
        permission: "MANAGE_ROLES",
        responses: {
            200: {
                body: "Role",
            },
            400: {
                body: "APIErrorResponse",
            },
            403: {
                body: "APIErrorResponse",
            },
        },
    }),
    async (req: Request, res: Response) => {
        const guild_id = req.params.guild_id as string;
        const body = req.body as RoleModifySchema;

        const role_count = await Role.count({ where: { guild_id } });
        const { maxRoles } = Config.get().limits.guild;

        if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);

        // TODO: proper field error
        if (body.name && body.name.length > 255) throw new Error("Role name must not exceed 255 characters");

        const everyoneRole = await Role.findOne({ where: { id: guild_id } });

        const role = Role.create({
            // values before ...body are default and can be overridden
            position: 1,
            hoist: false,
            color: 0,
            mentionable: false,
            ...body,
            guild_id: guild_id,
            managed: false,
            permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || everyoneRole?.permissions || 0)),
            tags: undefined,
            icon: undefined,
            unicode_emoji: undefined,
            id: Snowflake.generate(),
            colors: {
                primary_color: body.colors?.primary_color || body.color || 0,
                secondary_color: body.colors?.secondary_color || undefined, // gradient
                tertiary_color: body.colors?.tertiary_color || undefined, // "holographic"
            },
        });

        await Promise.all([
            role.save(),
            // Move all existing roles up one position, to accommodate the new role
            Role.createQueryBuilder("roles")
                .where({
                    guild: { id: guild_id },
                    name: Not("@everyone"),
                    id: Not(role.id),
                })
                .update({ position: () => "position + 1" })
                .execute(),
            emitEvent({
                event: "GUILD_ROLE_CREATE",
                guild_id,
                data: {
                    guild_id,
                    role: role,
                },
            } satisfies GuildRoleCreateEvent),
        ]);

        res.json(role);
    },
);

router.patch(
    "/",
    route({
        requestBody: "RolePositionUpdateSchema",
        permission: "MANAGE_ROLES",
        responses: {
            200: {
                body: "APIRoleArray",
            },
            400: {
                body: "APIErrorResponse",
            },
            403: {
                body: "APIErrorResponse",
            },
        },
    }),
    async (req: Request, res: Response) => {
        const { guild_id } = req.params as { [key: string]: string };
        const body = req.body as RolePositionUpdateSchema;

        await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position })));

        const roles = await Role.find({
            where: body.map((x) => ({ id: x.id, guild_id })),
        });

        await Promise.all(
            roles.map((x) =>
                emitEvent({
                    event: "GUILD_ROLE_UPDATE",
                    guild_id,
                    data: {
                        guild_id,
                        role: x,
                    },
                } satisfies GuildRoleUpdateEvent),
            ),
        );

        res.json(roles);
    },
);

export default router;
