import { Type, Static } from '@sinclair/typebox';
import { TAKList } from './types.js';
import { GUIDMatch, MissionOptions } from './mission.js';
import Commands, { CommandOutputFormat, type ParsedArgs } from '../commands.js';

export enum MissionSubscriberRole {
    MISSION_OWNER = 'MISSION_OWNER',
    MISSION_SUBSCRIBER = 'MISSION_SUBSCRIBER',
    MISSION_READONLY_SUBSCRIBER = 'MISSION_READONLY_SUBSCRIBER'
}

export enum MissionInviteType {
    CLIENT_UID = 'clientUid',
    CALLSIGN = 'callsign',
    USERNAME = 'userName',
    GROUP = 'group',
    TEAM = 'team'
}


export const MissionRole = Type.Object({
    name: Type.Optional(Type.String()),
    description: Type.Optional(Type.String()),
    permissions: Type.Optional(Type.Array(Type.String()))
});

export const MissionInvite = Type.Object({
    missionName: Type.Optional(Type.String()),
    invitee: Type.Optional(Type.String()),
    type: Type.Optional(Type.String()),
    creatorUid: Type.Optional(Type.String()),
    createTime: Type.Optional(Type.String()),
    token: Type.Optional(Type.String()),
    role: Type.Optional(MissionRole),
    missionGuid: Type.Optional(Type.String())
});

export const MissionInviteList = TAKList(MissionInvite);

/**
 * @class
 */
export default class MissionInviteCommands extends Commands {
    schema = {
        list: {
            description: 'List Mission Invites',
            params: Type.Object({}),
            query: Type.Object({}),
            formats: [ CommandOutputFormat.JSON ]
        },
        get: {
            description: 'Get Mission Invites for a Mission',
            params: Type.Object({
                name: Type.String()
            }),
            query: Type.Object({}),
            formats: [ CommandOutputFormat.JSON ]
        },
        invite: {
            description: 'Invite a user/group to a mission',
            params: Type.Object({
                name: Type.String(),
                type: Type.Enum(MissionInviteType),
                invitee: Type.String()
            }),
            query: Type.Object({
                creatorUid: Type.String(),
                role: Type.Optional(Type.Enum(MissionSubscriberRole))
            }),
            formats: [ CommandOutputFormat.JSON ]
        },
        uninvite: {
            description: 'Uninvite a user/group from a mission',
            params: Type.Object({
                name: Type.String(),
                type: Type.Enum(MissionInviteType),
                invitee: Type.String()
            }),
            query: Type.Object({
                creatorUid: Type.String()
            }),
            formats: [ CommandOutputFormat.JSON ]
        }
    }

    async cli(args: ParsedArgs): Promise<object | string> {
        const clientUid = typeof args.clientUid === 'string' ? args.clientUid : undefined;
        const token = typeof args.token === 'string' ? args.token : undefined;
        const creatorUid =
            typeof args.creatorUid === 'string' ? args.creatorUid : undefined;
        const role =
            typeof args.role === 'string'
                ? (args.role as MissionSubscriberRole)
                : undefined;

        if (args._[3] === 'list') {
            if (!clientUid) throw new Error('Usage: list --clientUid <clientUid>');

            const list = await this.list(clientUid, {
                token
            });

            if (args.format === 'json') {
                return list;
            } else {
                return list.data.map((invite) => {
                    return `${invite.missionName}: ${invite.invitee} (${invite.role?.name || 'Unknown Role'})`;
                }).join('\n');
            }
        } else if (args._[3] === 'get') {
            const name = args._[4];

            if (!name) throw new Error('Usage: get <name>');

            const list = await this.get(name, {
                token
            });

            if (args.format === 'json') {
                return list;
            } else {
                return list.data.map((invite) => {
                    return JSON.stringify(invite);
                }).join('\n');
            }
        } else if (args._[3] === 'invite') {
            const name = args._[4];
            const type = args._[5] as MissionInviteType;
            const invitee = args._[6];

            if (!name || !type || !invitee) throw new Error('Usage: invite <name> <type> <invitee>');
            if (!creatorUid) throw new Error('Usage: invite requires --creatorUid <uid>');

            await this.invite(name, type, invitee, {
                creatorUid,
                role
            }, {
                token
            });
            return { message: 'OK' };
        } else if (args._[3] === 'uninvite') {
            const name = args._[4];
            const type = args._[5] as MissionInviteType;
            const invitee = args._[6];

            if (!name || !type || !invitee) throw new Error('Usage: uninvite <name> <type> <invitee>');
            if (!creatorUid) throw new Error('Usage: uninvite requires --creatorUid <uid>');

            await this.uninvite(name, type, invitee, {
                creatorUid
            }, {
                token
            });
            return { message: 'OK' };
        } else {
            throw new Error('Unsupported Subcommand');
        }
    }

    #headers(opts?: Static<typeof MissionOptions>): object {
        if (opts && opts.token) {
            return {
                MissionAuthorization: `Bearer ${opts.token}`
            }
        } else {
            return {};
        }
    }

    /**
     * List all Invitations for missions
     *
     * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getAllMissionInvitations TAK Server Docs}.
     */
    async list(
        clientUid: string,
        opts?: Static<typeof MissionOptions>
    ): Promise<Static<typeof MissionInviteList>> {
        const url = new URL('/Marti/api/missions/invitations', this.api.url);

        url.searchParams.append('clientUid', clientUid);

        const invites = await this.api.fetch(url, {
            method: 'GET',
            headers: this.#headers(opts)
        });

        return invites;
    }

    /**
     * Get Mission Invitations
     *
     * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getMissionInvitations TAK Server Docs}.
     */
    async get(
        missionName: string,
        opts?: Static<typeof MissionOptions>
    ): Promise<Static<typeof MissionInviteList>> {
        let url;
        if (GUIDMatch.test(missionName)) {
            url = new URL(`/Marti/api/missions/guid/${encodeURIComponent(missionName)}/invitations`, this.api.url);
        } else {
            url = new URL(`/Marti/api/missions/${encodeURIComponent(missionName)}/invitations`, this.api.url);
        }

        return await this.api.fetch(url, {
            method: 'GET',
            headers: this.#headers(opts)
        });
    }

    /**
     * Invite a user/group to a mission
     *
     * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/inviteToMission TAK Server Docs}.
     */
    async invite(
        missionName: string,
        type: MissionInviteType,
        invitee: string,
        query: {
            creatorUid: string;
            role?: MissionSubscriberRole;
        },
        opts?: Static<typeof MissionOptions>
    ): Promise<void> {
        let url;
        if (GUIDMatch.test(missionName)) {
            url = new URL(`/Marti/api/missions/guid/${encodeURIComponent(missionName)}/invite/${encodeURIComponent(type)}/${encodeURIComponent(invitee)}`, this.api.url);
        } else {
            url = new URL(`/Marti/api/missions/${encodeURIComponent(missionName)}/invite/${encodeURIComponent(type)}/${encodeURIComponent(invitee)}`, this.api.url);
        }

        for (const q in query) url.searchParams.append(q, String(query[q as keyof typeof query]));

        await this.api.fetch(url, {
            method: 'PUT',
            headers: this.#headers(opts)
        });
    }

    /**
     * Uninvite a user/group from a mission
     *
     * {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/uninviteFromMission TAK Server Docs}.
     */
    async uninvite(
        missionName: string,
        type: MissionInviteType,
        invitee: string,
        query: {
            creatorUid: string;
        },
        opts?: Static<typeof MissionOptions>
    ): Promise<void> {
        let url;
        if (GUIDMatch.test(missionName)) {
            url = new URL(`/Marti/api/missions/guid/${encodeURIComponent(missionName)}/invite/${encodeURIComponent(type)}/${encodeURIComponent(invitee)}`, this.api.url);
        } else {
            url = new URL(`/Marti/api/missions/${encodeURIComponent(missionName)}/invite/${encodeURIComponent(type)}/${encodeURIComponent(invitee)}`, this.api.url);
        }

        for (const q in query) url.searchParams.append(q, String(query[q as keyof typeof query]));

        await this.api.fetch(url, {
            method: 'DELETE',
            headers: this.#headers(opts)
        });
    }
}
