import {
    UseGuards,
    NotFoundException,
    UnauthorizedException,
    ForbiddenException,
} from '@nestjs/common';
import {
    Args,
    Context,
    Mutation,
    Parent,
    ResolveField,
    Resolver,
    Subscription,
} from '@nestjs/graphql';
import { GraphQLString, GraphQLInt } from 'graphql';
import { Request } from 'express';
import { GroupAuthGuard } from '../auth/group-auth.guard';
import { PrismaService } from '../prisma/prisma.service';
import { TokenCode } from './models/tokenCode.model';
import { AgentAuthGuard } from '../auth/agent-auth.guard';
import { pubSub } from '../pubSub.instance';

@Resolver(() => TokenCode)
export class TokenCodeResolver {
    constructor(private prismaService: PrismaService) {}

    @Subscription(() => TokenCode, {
        filter: (payload, variables, state) =>
            payload.newTokenCode.agent.slug === variables.agentSlug,
    })
    async newTokenCode(
        @Args({ name: 'agentSlug', type: () => GraphQLString })
        agentSlug: string,
    ) {
        if (!agentSlug) throw new UnauthorizedException();

        const agent = await this.prismaService.agent.findFirst({
            where: {
                slug: agentSlug,
            },
        });

        if (!agent) throw new ForbiddenException();
        return pubSub.asyncIterator('newTokenCode');
    }

    @Mutation(() => TokenCode)
    async tokenCode(
        @Args({ name: 'agentSlug', type: () => GraphQLString }) agentSlug: string,
    ) {
        if (!agentSlug) throw new UnauthorizedException();

        const agent = await this.prismaService.agent.findFirst({
            where: {
                slug: agentSlug,
            },
        });

        if (!agent) throw new ForbiddenException();

        let tokenCode =
            agent.tokenCodeId &&
            (await this.prismaService.tokenCode.findFirst({
                where: {
                    id: agent.tokenCodeId,
                },
            }));

        if (!tokenCode) {
            tokenCode = await this.prismaService.tokenCode.create({
                data: {
                    value: 1,
                    agent: {
                        connect: { id: agent.id },
                    },
                },
                include: {
                    agent: true,
                },
            });

            await pubSub.publish('newTokenCode', {
                newTokenCode: tokenCode,
            });
        }

        return tokenCode;
    }

    @UseGuards(GroupAuthGuard)
    @Mutation(() => GraphQLInt)
    async redeemCode(
        @Args({ name: 'tokenCode', type: () => GraphQLString }) code: string,
        @Context() { req }: { req: Request },
    ) {
        const tokenCode = await this.prismaService.tokenCode.findFirst({
            where: { id: code },
            include: {
                agent: true,
            },
        });

        if (!tokenCode) throw new NotFoundException();

        const newTokenCode = await this.prismaService.tokenCode.create({
            data: {
                value: 1,
                agent: {
                    connect: { id: tokenCode.agent.id },
                },
            },
            include: {
                agent: true,
            },
        });

        await pubSub.publish('newTokenCode', {
            newTokenCode: newTokenCode,
        });

        await this.prismaService.tokenCode.delete({
            where: { id: tokenCode.id },
        });

        req.group = await this.prismaService.group.update({
            where: { id: req.group.id },
            data: {
                tokens: {
                    increment: tokenCode.value,
                },
            },
        });

        await pubSub.publish('updateToken', {
            updateToken: req.group,
        });

        return tokenCode.value;
    }

    @ResolveField('agent')
    agent(@Parent() token: TokenCode) {
        return this.prismaService.agent.findFirst({
            where: {
                tokenCodeId: token.id,
            },
        });
    }
}