import { Context, GqlContext } from '@server/decorators/gql-context'
import { requireAuth } from '@server/guards/require-auth'
import {
  Args,
  ArgsType,
  Field,
  FieldResolver,
  Int,
  Mutation,
  ObjectType,
  Query,
  Resolver,
  Root,
} from 'type-graphql'
import { SORT_ORDER, UserPublicInfo } from './resolver.types'
import { renderMarkdown } from '@server/lib/markdown'
import { Topic } from './topic.resolver'
import { notificationQueue } from '@server/queues/notification.queue'
import { prisma } from '@server/lib/prisma'
import { ApolloError } from 'apollo-server-errors'

@ArgsType()
class CreateCommentArgs {
  @Field((type) => Int)
  topicId: number

  @Field()
  content: string

  @Field((type) => Int, {
    nullable: true,
  })
  parentId?: number
}

@ObjectType()
export class Comment {
  @Field((type) => Int)
  id: number

  @Field()
  createdAt: Date

  @Field()
  updatedAt: Date

  @Field()
  content: string

  @Field((type) => Int)
  authorId: number

  @Field((type) => Int, { nullable: true })
  parentId: number

  @Field((type) => Int)
  topicId: number
}

@ObjectType()
class CommentsConnection {
  @Field((type) => [Comment])
  items: Comment[]

  @Field()
  hasNext: boolean

  @Field()
  hasPrev: boolean

  @Field((type) => Int)
  total: number
}

@ArgsType()
class CommentsArgs {
  @Field((type) => Int)
  topicId: number

  @Field((type) => Int, {
    defaultValue: 100,
  })
  take: number

  @Field((type) => Int, {
    defaultValue: 1,
  })
  page: number

  @Field((type) => SORT_ORDER, { defaultValue: 'asc' })
  order: 'desc' | 'asc'
}

@ArgsType()
class LikeCommentArgs {
  @Field((type) => Int)
  commentId: number
}

@Resolver((of) => Comment)
export class CommentResolver {
  @Query((returns) => CommentsConnection)
  async comments(@Args() args: CommentsArgs) {
    const skip = (args.page - 1) * args.take

    const comments = await prisma.comment.findMany({
      where: {
        topicId: args.topicId,
      },
      take: args.take + 1,
      skip,
      orderBy: {
        createdAt: args.order,
      },
    })
    const count = await prisma.comment.count({
      where: {
        topicId: args.topicId,
      },
    })

    return {
      items: comments.slice(0, args.take),
      hasNext: comments.length > args.take,
      hasPrev: args.page > 1,
      total: count,
    }
  }

  @Mutation((returns) => Comment)
  async createComment(
    @GqlContext() ctx: Context,
    @Args() args: CreateCommentArgs,
  ) {
    const user = requireAuth(ctx)
    const comment = await prisma.comment.create({
      data: {
        topic: {
          connect: {
            id: args.topicId,
          },
        },
        content: args.content,
        author: {
          connect: {
            id: user.id,
          },
        },
        parent: args.parentId
          ? {
              connect: {
                id: args.parentId,
              },
            }
          : undefined,
      },
    })

    await prisma.topic.update({
      where: {
        id: args.topicId,
      },
      data: {
        lastComment: {
          connect: {
            id: comment.id,
          },
        },
      },
    })

    // Add notification
    notificationQueue.add({ commentId: comment.id })

    return comment
  }

  @FieldResolver((returns) => String)
  html(@Root() comment: Comment) {
    const html = renderMarkdown(comment.content)
    return html
  }

  @FieldResolver((returns) => UserPublicInfo)
  async author(@Root() comment: Comment) {
    const author = await prisma.user.findUnique({
      where: {
        id: comment.authorId,
      },
    })
    return author
  }

  @FieldResolver((returns) => Comment, {
    nullable: true,
  })
  async parent(@Root() comment: Comment) {
    const parentComment =
      comment.parentId &&
      (await prisma.comment.findUnique({
        where: {
          id: comment.parentId,
        },
      }))
    return parentComment
  }

  @FieldResolver((returns) => Topic)
  async topic(@Root() comment: Comment) {
    const topic = await prisma.topic.findUnique({
      where: {
        id: comment.topicId,
      },
    })
    return topic
  }

  @FieldResolver((returns) => Int)
  async likesCount(@Root() comment: Comment) {
    const count = await prisma.userCommentLike.count({
      where: {
        commentId: comment.id,
      },
    })
    return count
  }

  @FieldResolver((returns) => Boolean)
  async isLiked(@GqlContext() ctx: Context, @Root() comment: Comment) {
    if (!ctx.user) {
      return false
    }

    const record = await prisma.userCommentLike.findFirst({
      where: {
        userId: ctx.user.id,
        commentId: comment.id,
      },
    })
    return Boolean(record)
  }

  @Mutation((returns) => Boolean)
  async likeComment(@GqlContext() ctx: Context, @Args() args: LikeCommentArgs) {
    const user = requireAuth(ctx)
    const comment = await prisma.comment.findUnique({
      where: {
        id: args.commentId,
      },
    })
    if (!comment) throw new ApolloError(`comment was not found`)
    let userCommentLike = await prisma.userCommentLike.findFirst({
      where: {
        commentId: comment.id,
        userId: user.id,
      },
    })
    let liked = false
    if (userCommentLike) {
      await prisma.userCommentLike.delete({
        where: {
          id: userCommentLike.id,
        },
      })
    } else {
      userCommentLike = await prisma.userCommentLike.create({
        data: {
          commentId: comment.id,
          userId: user.id,
        },
      })
      liked = true
    }
    return liked
  }
}