import {Args, Context, Float, Int, Mutation, Resolver} from '@nestjs/graphql';
import { Event } from '../models/Event';
import { Client } from '../../client';
import { EventService } from '../event.service';
import { HttpException } from '@nestjs/common';
import {Date} from '../../global/scalars/Date'
import {UUID} from '../../global/scalars/UUID'
import {OrganizerService} from '../../organizer/organizer.service'
import {pubsub} from '../../main'
import {ITimeslot, Timeslot} from '../models/Timeslot'
import { v4 as uuid } from 'uuid'
import {IResult} from '../models/Result'
import {Time} from '../../global/scalars/Time'

@Resolver(() => Event)
export class EventResolverM {
  constructor(
    private readonly service: EventService,
  ) {}

  @Mutation(() => Event, { nullable: false })
  async EventCreate(
    @Context('client') client: Client,
    @Args('organizer') organizer: UUID,
    @Args('date') date: Date,
    @Args('name', { nullable: true }) name?: string,
  ): Promise<Event> {
    if (!client.isMaster() && !client.isOrganizer(organizer)) {
      throw new HttpException('You\'re not allowed to create an Event for this organizer', 403);
    }

    const organizerService = new OrganizerService();
    const o = await organizerService.findOneById(organizer);

    if (!o) {
      throw new HttpException('Organizer-ID not found!', 404);
    }

    const neu = await this.service.create(client, date, organizer, name);

    pubsub.publish('EventUpdated', { EventUpdated: neu });

    return neu;
  }

  @Mutation(() => Event, { nullable: false })
  async EventUpdate(
    @Context('client') client, Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('name', { type: () => String, nullable: true }) name: string,
    @Args('date', { type: () => Date, nullable: true }) date: Date
  ): Promise<Event> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    const set: {
      name?: string,
      date?: Date,
    } = {};

    if (name !== undefined && name !== tmp.name) set.name = name;
    if (date !== undefined && date !== tmp.date) set.date = date;

    if (Object.values(set).length === 0) return tmp;

    const neu = await this.service.update(client, tmp._id, {$set: set});
    pubsub.publish('EventUpdated', { EventUpdated: neu });
    return neu;
  }

  @Mutation(() => Event, { nullable: false })
  async EventAddApparatus(
    @Context('client') client, Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('apparatus', { type: () => UUID, nullable: false }) apparatus: UUID,
  ): Promise<Event> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    let neu: Event;

    if (!tmp.apparatus) {
      neu = await this.service.update(client, tmp._id, {$set: {apparatus: [{ _apparatus: apparatus, elements: 3, bonus: 3.00, malus: 5.00 }]}});
    } else {
      neu = await this.service.update(client, tmp._id, {$push: { apparatus: { _apparatus: apparatus, elements: 3, bonus: 3.00, malus: 5.00 }}})
    }
    pubsub.publish('EventUpdated', { EventUpdated: neu });
    return neu;
  }

  @Mutation(() => Event, { nullable: false })
  async EventDeleteApparatus(
    @Context('client') client, Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('apparatus', { type: () => UUID, nullable: false }) apparatus: UUID,
  ): Promise<Event> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    let neu: Event;

    if (!tmp.apparatus) {
      neu = tmp;
    } else {
      neu = await this.service.update(client, tmp._id, {$pull: { apparatus: { _apparatus: apparatus }}})
    }
    pubsub.publish('EventUpdated', { EventUpdated: neu });
    return neu;
  }

  @Mutation(() => Event, { nullable: false })
  async EventUpdateApparatus(
    @Context('client') client, Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('apparatus', { type: () => UUID, nullable: false }) apparatus: UUID,
    @Args('elements', { type: () => Int, nullable: true }) elements?: number,
    @Args('bonus', { type: () => Float, nullable: true }) bonus?: number,
    @Args('malus', { type: () => Float, nullable: true }) malus?: number
  ): Promise<Event> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    const set: any = {
      'apparatus.$[apparatus]': {
        _apparatus: apparatus
      }
    }

    if (elements !== undefined && elements !== null) set['apparatus.$[apparatus]'].elements = elements
    if (bonus !== undefined && bonus !== null) set['apparatus.$[apparatus]'].bonus = bonus
    if (malus !== undefined && malus !== null) set['apparatus.$[apparatus]'].malus = malus

    const neu: Event = await this.service.update(client, tmp._id, { $set: set }, { arrayFilters: [ { 'apparatus._apparatus': apparatus } ] })

    pubsub.publish('EventUpdated', { EventUpdated: neu });
    return neu;
  }

  @Mutation(() => Event, { nullable: false })
  async EventOrderApparatus(
    @Context('client') client: Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('src', { type: () => Int, nullable: false }) src: number,
    @Args('target', { type: () => Int, nullable: false }) target: number,
  ): Promise<Event> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    let neu: Event;

    if (!tmp.apparatus) {
      neu = tmp;
    } else {
      const app = tmp.apparatus.splice(src, 1)
      if (app?.length === 1) tmp.apparatus.splice(target, 0, app[0])
      neu = await this.service.update(client, tmp._id, {$set: { apparatus: tmp.apparatus }})
    }
    pubsub.publish('EventUpdated', { EventUpdated: neu });
    return neu;
  }

  @Mutation(() => Event, { nullable: false })
  async EventAddTimeslots(
    @Context('client') client: Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('timeslots', { type: () => [ITimeslot], nullable: false}) timeslots: ITimeslot[],
  ): Promise<Event> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    let neu: Event;

    if (!tmp.timeslots) {
      neu = await this.service.update(client, tmp._id, {$set: {timeslots: timeslots.map(t => ({_id: uuid(), ...t }))}});
    } else {
      timeslots = timeslots.filter(n => !tmp.timeslots.find(o => o.time === n.time && o.duration === n.duration));
      neu = await this.service.update(client, tmp._id, {$push: { timeslots: { $each: timeslots.map(t => ({_id: uuid(), ...t })) }}});
    }

    pubsub.publish('EventUpdated', { EventUpdated: neu });
    return neu;
  }

  @Mutation(() => Event, { nullable: false })
  async EventDeleteTimeslots(
    @Context('client') client: Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('timeslots', { type: () => [UUID], nullable: false}) timeslots: UUID[],
  ): Promise<Event> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    let neu: Event;

    if (!tmp.timeslots) {
      neu = tmp;
    } else {
      neu = await this.service.update(client, tmp._id, {$pull: { timeslots: { _id: { $in: timeslots } }}});
    }

    pubsub.publish('EventUpdated', { EventUpdated: neu });
    return neu;
  }

  @Mutation(() => Event, { nullable: false })
  async EventAddTeam(
    @Context('client') client: Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('timeslot', { type: () => UUID, nullable: false }) timeslot: UUID,
    @Args('name', { type: () => String, nullable: false }) name: string
  ): Promise<Event> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    if (!tmp?.timeslots?.find(t => t._id === timeslot)) {
      throw new HttpException('Timeslot-ID not found', 404);
    }

    let neu: Event;

    if (tmp.timeslots.find(t => t._id === timeslot).team) {
      neu = await this.service.update(
        client,
        tmp._id,
        { $set: { 'timeslots.$[ts].team.name': name }},
        { arrayFilters: [{ 'ts._id': timeslot }]}
      );
    } else {
      neu = await this.service.update(
        client,
        tmp._id,
        { $set: { 'timeslots.$[ts].team': { _id: uuid(), name }}},
        { arrayFilters: [{ 'ts._id': timeslot }]}
      );
    }

    pubsub.publish('EventUpdated', { EventUpdated: neu });
    return neu;
  }

  @Mutation(() => Event, { nullable: false })
  async EventDeleteTeam(
    @Context('client') client: Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('timeslot', { type: () => UUID, nullable: false }) timeslot: UUID,
  ): Promise<Event> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    if (!tmp?.timeslots?.find(t => t._id === timeslot)) {
      throw new HttpException('Timeslot-ID not found', 404);
    } else if (!tmp.timeslots.find(t => t._id === timeslot).team) {
      throw new HttpException('Timeslot empty', 404);
    }

    const neu: Event = await this.service.update(
      client,
      tmp._id,
      { $unset: { 'timeslots.$[ts].team': '', 'timeslots.$[ts].result': '' }},
      { arrayFilters: [{ 'ts._id': timeslot }]}
    );

    pubsub.publish('EventUpdated', { EventUpdated: neu });
    return neu;
  }

  @Mutation(() => Timeslot, { nullable: false })
  async EventSetResult(
    @Context('client') client: Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('timeslot', { type: () => UUID, nullable: false }) timeslot: UUID,
    @Args('result', { type: () => IResult, nullable: false }) result: IResult
  ): Promise<Timeslot> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    if (!tmp?.timeslots?.find(t => t._id === timeslot)) {
      throw new HttpException('Timeslot-ID not found', 404);
    } else if (!tmp.timeslots.find(t => t._id === timeslot).team) {
      throw new HttpException('Timeslot empty', 404);
    }

    const neu: Event = await this.service.update(
      client,
      tmp._id,
      { $set: { 'timeslots.$[ts].result': result }},
      { arrayFilters: [{ 'ts._id': timeslot }]}
    );

    pubsub.publish('EventUpdated', { EventUpdated: neu });
    return neu.timeslots.find(t => t._id === timeslot);
  }

  @Mutation(() => UUID, { nullable: false })
  async EventDelete(
    @Context('client') client, Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
  ): Promise<UUID> {
    const tmp = await this.service.findOneById(id);
    if (!tmp) throw new HttpException('Event-ID not found!', 404);

    if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) {
      throw new HttpException('Access denied', 403);
    }

    this.service.delete(client, id);

    pubsub.publish('EventDeleted', { EventDeleted: id, organizer: tmp._organizer });

    return id;
  }
}
