import {
	Args,
	Context,
	Mutation,
	Parent,
	Query,
	ResolveField,
	Resolver,
	Subscription,
} from '@nestjs/graphql';
import { EntryModel } from './models/entry.model';
import { PrismaService } from '../prisma/prisma.service';
import {
	BadRequestException,
	InternalServerErrorException,
	NotFoundException,
	UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';
import { Request } from 'express';
import { EntryService } from '../utils/entry.service';
import { AgentModel } from './models/agent.model';
import { GroupGuard } from '../auth/group.guard';
import { PubSub } from 'graphql-subscriptions';
import { Entry, Group } from '@prisma/client';

@Resolver(() => EntryModel)
export class EntryResolver {
	constructor(
		private readonly pubSub: PubSub,
		private readonly prisma: PrismaService,
		private readonly entryService: EntryService,
	) {}

	@Subscription(() => EntryModel, {
		filter: (payload: Entry & { by?: Group }, variables, { req }) => {
			if (payload.by) return req.group.id === payload.by.id;

			return true;
		},
	})
	onEntryUpdate(@Context('pubsub') pubSub: PubSub) {
		return pubSub.asyncIterator('onEntryUpdate');
	}

	@Mutation(() => EntryModel)
	@UseGuards(AuthGuard, GroupGuard)
	unlockEntry(
		@Args('id') id: string,
		@Context() { req }: { req: Request },
	): Promise<EntryModel> {
		return this.prisma.$transaction(async (prisma) => {
			const entry = await prisma.entry.findFirst({
				where: {
					id,
					unlockedBy: {
						none: { id: req.group.id },
					},
				},
			});

			if (!entry) throw new NotFoundException();

			if (req.group.tokens < entry.cost)
				throw new BadRequestException('Not enough tokens');

			const group = await prisma.group.update({
				where: { id: req.group.id },
				data: {
					tokens: { decrement: entry.cost },
					unlocks: {
						connect: { id: entry.id },
					},
				},
			});

			if (!group)
				throw new InternalServerErrorException('Group not found');

			await this.pubSub.publish('onEntryUpdate', {
				...entry,
				by: group,
			});

			return this.entryService.unlockedEntry(entry);
		});
	}

	@Query(() => [EntryModel])
	@UseGuards(AuthGuard)
	async getEntries(@Context() { req }: { req: Request }) {
		if (req.agent)
			return this.prisma.entry
				.findMany()
				.then((entries) =>
					entries.map(this.entryService.unlockedEntry),
				);

		return await this.entryService.getAccessibleEntries(req.group.id);
	}

	@ResolveField('agent')
	async agent(@Parent() parent: EntryModel): Promise<AgentModel> {
		return this.prisma.agent.findFirst({
			where: {
				entries: {
					some: { id: parent.id },
				},
			},
			select: {
				id: true,
				name: true,
				slug: false,
				flags: true,
			},
		});
	}
}