import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { NotFoundException, UseGuards } from '@nestjs/common';
import {
  AccessService,
  AccessGuard,
  Actions,
  ConditionsProxy,
  CaslConditions,
  CaslSubject,
  CaslUser,
  UseAbility,
  UserProxy,
  SubjectProxy,
} from 'nest-casl';

import { User } from '../user/dtos/user.dto';
import { Post } from './dtos/post.dto';
import { PostHook } from './post.hook';
import { PostService } from './post.service';
import { CreatePostInput } from './dtos/create-post-input.dto';
import { UpdatePostInput } from './dtos/update-post-input.dto';

@Resolver(() => Post)
export class PostResolver {
  constructor(private postService: PostService, private accessService: AccessService) {}

  @Query(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility(Actions.read, Post)
  async post(@Args('id') id: string) {
    return this.postService.findById(id);
  }

  @Query(() => [Post])
  @UseGuards(AccessGuard)
  @UseAbility(Actions.read, Post)
  async posts() {
    return this.postService.findAll();
  }

  @Mutation(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility(Actions.create, Post)
  async createPost(@Args('input') input: CreatePostInput) {
    return this.postService.create(input);
  }

  @Mutation(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility(Actions.delete, Post)
  async deletePost(@Args('id') id: string) {
    return this.postService.delete(id);
  }

  @Mutation(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility(Actions.update, Post, PostHook)
  async updatePost(@Args('input') input: UpdatePostInput) {
    return this.postService.update(input);
  }

  @Mutation(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility<Post>(Actions.update, Post, [
    PostService,
    (service: PostService, { params }) => service.findById(params.input.id),
  ])
  async updatePostTupleHook(@Args('input') input: UpdatePostInput) {
    return this.postService.update(input);
  }

  @Mutation(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility(Actions.update, Post)
  async updatePostNoHook(@Args('input') input: UpdatePostInput) {
    return this.postService.update(input);
  }

  @Mutation(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility(Actions.update, Post, PostHook)
  async updatePostUserParam(@Args('input') input: UpdatePostInput, @CaslUser() user: UserProxy<User>) {
    this.postService.addUser(await user.get());
    return this.postService.update(input);
  }

  @Mutation(() => Post)
  async updatePostUserParamNoAbility(@Args('input') input: UpdatePostInput, @CaslUser() user: UserProxy<User>) {
    this.postService.addUser(await user.get());
    return this.postService.update(input);
  }

  @Mutation(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility(Actions.update, Post, PostHook)
  async updatePostSubjectParam(@Args('input') input: UpdatePostInput, @CaslSubject() subjectProxy: SubjectProxy<Post>) {
    const post = await subjectProxy.get();
    if (!post) {
      throw new NotFoundException('Post not found');
    }
    return this.postService.update(post);
  }

  @Mutation(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility<Post>(Actions.update, Post, [
    PostService,
    (service: PostService, { params }) => service.findById(params.input.id),
  ])
  async updatePostSubjectParamTuple(
    @Args('input') input: UpdatePostInput,
    @CaslSubject() subjectProxy: SubjectProxy<Post>,
  ) {
    const post = await subjectProxy.get();
    if (!post) {
      throw new NotFoundException('Post not found');
    }
    return this.postService.update(post);
  }

  @Mutation(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility(Actions.update, Post, PostHook)
  async updatePostConditionParam(@Args('input') input: UpdatePostInput, @CaslConditions() conditions: ConditionsProxy) {
    return this.postService.update(input, conditions.toSql());
  }

  @Mutation(() => Post)
  @UseGuards(AccessGuard)
  @UseAbility(Actions.update, Post)
  async updatePostConditionParamNoHook(
    @Args('input') input: UpdatePostInput,
    @CaslConditions() conditions: ConditionsProxy,
  ) {
    return this.postService.update(input, conditions.toSql());
  }
}