import {
  Args,
  Context,
  Int,
  Parent,
  Query,
  ResolveField,
  Resolver,
  Mutation,
  Subscription,
  ID,
} from '@nestjs/graphql';
import { UserType } from '../../../types/user.type';
import { PostType } from '../../../types/post.type';
import {
  LoaderQuery,
  ResolveLoader,
  toAsyncIterator,
} from '../../../../../lib';
import { UserService } from '../services/user.service';
import { PostService } from '../services/post.service';
import { CreateUserInput } from '../inputs/create-user.input';
import { MercuriusContext } from 'mercurius';
import {
  ParseIntPipe,
  UseFilters,
  UseInterceptors,
  UseGuards,
} from '@nestjs/common';
import { LogInterceptor } from '../../../interceptors/log.interceptor';
import { ForbiddenExceptionFilter } from '../../../filters/forbidden-exception.filter';
import { Header } from '../../../decorators/header.decorator';
import { FullNameArgs } from '../inputs/full-name.args';
import { AuthGuard } from '../../../guards/auth.guard';

function calculateAge(birthday: Date): number {
  const ageDifMs = Date.now() - birthday.getTime();
  const ageDate = new Date(ageDifMs);
  return Math.abs(ageDate.getUTCFullYear() - 1970);
}

@UseFilters(ForbiddenExceptionFilter)
@Resolver(() => UserType)
export class UserResolver {
  constructor(
    private readonly userService: UserService,
    private readonly postService: PostService,
  ) {}

  @Query(() => [UserType], {
    complexity: (options) => options.childComplexity + 5,
  })
  users() {
    return this.userService.users();
  }

  @Query(() => UserType, { nullable: true })
  user(@Args({ name: 'id', type: () => ID }, ParseIntPipe) id: number) {
    return this.userService.find(id);
  }

  @Mutation(() => UserType)
  createUser(
    @Args({ name: 'input', type: () => CreateUserInput }) data: CreateUserInput,
    @Context() ctx: MercuriusContext,
  ) {
    const user = this.userService.create(data);

    return user;
  }

  @ResolveField(() => Int, { complexity: 5 })
  async age(
    @Parent() user: UserType,
    @Context('headers') headers: Record<string, any>,
  ) {
    return calculateAge(user.birthDay);
  }

  @ResolveLoader(() => String, {
    nullable: true,
    complexity: (options) => {
      return 5;
    },
    middleware: [
      async (ctx, next) => {
        const results = await next();
        return results.map((res) => res || 'Missing');
      },
    ],
    opts: {
      cache: false,
    },
  })
  async fullName(
    @Args({ type: () => FullNameArgs }) args: FullNameArgs[],
    @Parent() p: LoaderQuery<UserType>[],
    @Context() ctx: Record<string, any>,
    @Header('authorization') auth?: string,
  ) {
    return p.map(({ obj }) => {
      if (obj.name && obj.lastName) {
        return `${obj.name} ${obj.lastName}`;
      }
      return obj.name || obj.lastName;
    });
  }

  // @UseGuards(AuthGuard)
  @UseInterceptors(LogInterceptor)
  @ResolveLoader(() => [PostType])
  async posts(@Parent() queries: LoaderQuery<UserType>[]) {
    return this.postService.userPostLoader(queries.map((q) => q.obj.id));
  }

  @UseInterceptors(LogInterceptor)
  @Subscription(() => UserType)
  async userAdded(@Context() ctx: MercuriusContext) {
    return ctx.pubsub.subscribe('USER_ADDED');
  }

  @Subscription(() => String, {
    resolve: (payload) => {
      return payload.userAdded.id;
    },
  })
  async userAddedId(@Context() ctx: MercuriusContext) {
    return ctx.pubsub.subscribe('USER_ADDED');
  }

  @Subscription(() => UserType, {
    filter: (payload, variables: { id: string }) => {
      return payload.userAdded.id === variables.id;
    },
    resolve: (payload) => {
      return payload.userAdded;
    },
  })
  async specificUserAdded(
    @Args({ name: 'id', type: () => ID }) id: string,
    @Context() ctx: MercuriusContext,
  ) {
    return toAsyncIterator(ctx.pubsub.subscribe('USER_ADDED'));
  }
}