import { Context, Parent, ResolveField, Resolver } from '@nestjs/graphql'
import { Roles } from '../../decorator/roles.decorator'
import { User } from '../../decorator/user.decorator'
import { DesignModel } from '../../dto/form/design.model'
import { FormFieldModel } from '../../dto/form/form.field.model'
import { FormHookModel } from '../../dto/form/form.hook.model'
import { FormModel } from '../../dto/form/form.model'
import { FormNotificationModel } from '../../dto/form/form.notification.model'
import { PageModel } from '../../dto/form/page.model'
import { UserModel } from '../../dto/user/user.model'
import { FormEntity } from '../../entity/form.entity'
import { FormFieldEntity } from '../../entity/form.field.entity'
import { PageEntity } from '../../entity/page.entity'
import { UserEntity } from '../../entity/user.entity'
import { FormService } from '../../service/form/form.service'
import { IdService } from '../../service/id.service'
import { ContextCache } from '../context.cache'

@Resolver(() => FormModel)
export class FormResolver {
  constructor(
    private readonly formService: FormService,
    private readonly idService: IdService,
  ) {
  }

  @ResolveField(() => [FormFieldModel])
  async fields(
    @User() user: UserEntity,
    @Parent() parent: FormModel,
    @Context('cache') cache: ContextCache,
  ): Promise<FormFieldModel[]> {
    const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))

    if (!form.fields) {
      return []
    }

    return form.fields
      .sort((a,b) => a.idx - b.idx)
      .map(field => {
        cache.add(cache.getCacheKey(FormFieldEntity.name, field.id), field)

        return new FormFieldModel(
          this.idService.encode(field.id),
          field,
        )
      })
  }

  @ResolveField(() => [FormHookModel])
  @Roles('admin')
  async hooks(
    @User() user: UserEntity,
    @Parent() parent: FormModel,
    @Context('cache') cache: ContextCache,
  ): Promise<FormHookModel[]> {
    const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))

    if (!this.formService.isAdmin(form, user)) {
      throw new Error('no access to field')
    }

    if (!form.hooks) {
      return []
    }

    return form.hooks.map(hook => new FormHookModel(
      this.idService.encode(hook.id),
      hook
    ))
  }

  @ResolveField(() => Boolean)
  @Roles('admin')
  async isLive(
    @User() user: UserEntity,
    @Parent() parent: FormModel,
    @Context('cache') cache: ContextCache,
  ): Promise<boolean> {
    const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))

    if (!this.formService.isAdmin(form, user)) {
      throw new Error('no access to field')
    }

    return form.isLive
  }

  @ResolveField(() => [FormNotificationModel])
  @Roles('admin')
  async notifications(
    @User() user: UserEntity,
    @Parent() parent: FormModel,
    @Context('cache') cache: ContextCache,
  ): Promise<FormNotificationModel[]> {
    const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))

    if (!this.formService.isAdmin(form, user)) {
      throw new Error('no access to field')
    }

    if (!form.notifications) {
      return []
    }

    return form.notifications.map(notification => new FormNotificationModel(
      this.idService.encode(notification.id),
      notification
    ))
  }

  @ResolveField(() => DesignModel)
  async design(
    @User() user: UserEntity,
    @Parent() parent: FormModel,
    @Context('cache') cache: ContextCache,
  ): Promise<DesignModel> {
    const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))

    return new DesignModel(form.design)
  }

  @ResolveField(() => PageModel)
  async startPage(
    @Parent() parent: FormModel,
    @Context('cache') cache: ContextCache,
  ): Promise<PageModel> {
    const { startPage } = await cache.get<FormEntity>(cache.getCacheKey(
      FormEntity.name,
      parent._id
    ))

    if (startPage) {
      cache.add(cache.getCacheKey(PageEntity.name, startPage.id), startPage)

      return new PageModel(
        this.idService.encode(startPage.id),
        startPage
      )
    }

    return new PageModel(Math.random().toString())
  }

  @ResolveField(() => PageModel)
  async endPage(
    @Parent() parent: FormModel,
    @Context('cache') cache: ContextCache,
  ): Promise<PageModel> {
    const { endPage } = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))

    if (endPage) {
      cache.add(cache.getCacheKey(PageEntity.name, endPage.id), endPage)

      return new PageModel(
        this.idService.encode(endPage.id),
        endPage
      )
    }

    return new PageModel(Math.random().toString())
  }

  @ResolveField(() => UserModel, { nullable: true })
  @Roles('admin')
  async admin(
    @Parent() parent: FormModel,
    @Context('cache') cache: ContextCache,
  ): Promise<UserModel> {
    const form = await cache.get<FormEntity>(cache.getCacheKey(FormEntity.name, parent._id))

    if (!form.admin) {
      return null
    }

    return new UserModel(this.idService.encode(form.admin.id), form.admin)
  }
}