import { Args, Context, Mutation, Resolver } from '@nestjs/graphql';
import { Person } from '../models/Person';
import { Client } from '../../client';
import { PersonService } from '../person.service';
import { HttpException } from '@nestjs/common';
import {UUID} from '../../global/scalars/UUID'
import {OrganizerService} from '../../organizer/organizer.service'
import {EmailAddress} from '../../global/scalars/EmailAddress'
import {checkPassword, secureHash} from '../../generate'
import {pubsub} from '../../main'
import {sendmail} from '../../mail'
import {SERVERDATA, SYSDATA} from '../../config'
import { v4 as uuid } from 'uuid'

@Resolver(() => Person)
export class PersonResolverM {
  constructor(
    private readonly service: PersonService,
  ) {}

  @Mutation(() => Person, { nullable: false })
  async login(
    @Context('client') client: Client,
    @Args('email', { nullable: true }) email?: string,
    @Args('passwort', { nullable: true }) passwort?: string,
    @Args('token', { nullable: true }) token?: string
  ): Promise<Person> {
    await client.login({email, passwort, token});
    const newtoken: string = client.getToken();

    if (!newtoken) {
      throw new HttpException('Logindaten falsch', 403);
    }

    const tmp = await this.service.findOneById(client.getUser()?._id);

    if (!!(tmp as unknown as any).confirmCode) {
      throw new HttpException('E-Mail-Adresse noch nicht bestätigt!', 403);
    }

    return tmp;
  }

  @Mutation(() => Person, { nullable: false })
  async PersonRegister(
    @Context('client') client: Client,
    @Args('organizer', { type: () => UUID, nullable: false }) organizer: UUID,
    @Args('givenName', { nullable: false }) givenName: string,
    @Args('familyName', { nullable: false }) familyName: string,
    @Args('email', { type: () => EmailAddress, nullable: false }) email: EmailAddress,
    @Args('passwort', { nullable: false }) passwort: string,
  ): Promise<Person> {
    const organizerService = new OrganizerService();
    const o = await organizerService.findOneById(organizer);

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

    const tmp = await this.service.create(client, givenName, familyName, email, passwort);

    let isAdmin = false;

    if (!o._admins) {
      await organizerService.update(client, o._id, {$set: {_admins: [ tmp._id ] }}, {});
      isAdmin = true;
    } else if (o._admins.length === 0) {
      await organizerService.update(client, o._id, {$push: {_admins: tmp._id }}, {});
      isAdmin = true;
    } else if (!o._pending) {
      await organizerService.update(client, o._id, {$set: {_pending: [ tmp._id ] }}, {});
    } else {
      await organizerService.update(client, o._id, {$push: {_pending: tmp._id }}, {});
    }

    const link = `${SERVERDATA.host}/confirm/${(tmp as unknown as any).confirmCode}`
    const text = `Vielen Dank für Ihre Registrierung bei ${SYSDATA.name}

Bitte bestätigen Sie unter folgendem Link Ihre Registrierung:

${link}` + (isAdmin?'':`

Sie müssen außerdem zunächst von einem Administrator Ihrer Schule/Ihres Vereins freigeschaltet werden. Dieser erhält ebenfalls eine E-Mail über Ihre Registrierung.`);

    sendmail(email,`[${SYSDATA.name}] Neu-Registrierung`, text);

    if (!isAdmin) {
      const admintext = `${givenName} ${familyName} hat sich neu bei ${SYSDATA.name} für Ihre Schule/Ihren Verein ${o.name} registriert.

Bitte melden Sie sich unter ${SERVERDATA.host} an und bearbeiten die Registrierung.`

      for (let i = 0; i < o._admins?.length; i++) {
        const p = await this.service.findOneById(o._admins[i]);

        sendmail(p.email, `[${SYSDATA.name}] Neu-Registrierung für ${o.name}`, admintext);
      }
    }

    pubsub.publish('PersonUpdated', { PersonUpdated: tmp });

    return tmp;
  }

  @Mutation(() => Person, { nullable: true })
  async PersonConfirmMail(
    @Context('client') client: Client,
    @Args('email') email: string,
    @Args('confirmCode') confirmCode: string,
  ): Promise<Person> {
    const tmp = await this.service.find({email, confirmCode});

    if (tmp.length !== 1) {
      throw new HttpException('E-Mail-Adresse oder Bestätigungscode ungültig', 403);
    }

    this.service.update(client, tmp[0]._id, { $unset: { confirmCode } })
    return tmp[0];
  }

  @Mutation(() => Boolean, { nullable: true })
  async PersonPasswordReset(
    @Context('client') client: Client,
    @Args('email') email: EmailAddress,
    @Args('resetCode', { type: () => String, nullable: true }) resetCode?: string,
    @Args('newPassword', { type: () => String, nullable: true }) newPassword?: string,
  ): Promise<boolean> {
    const tmp = await this.service.find({email});

    console.log((tmp[0] as unknown as any).resetCode)
    console.log(resetCode)

    if (tmp.length === 1) {
      if (!resetCode && !newPassword) {
        const code = uuid();
        this.service.update(client, tmp[0]._id, {$set: {resetCode: code}})

        const link = `${SERVERDATA.host}/reset/${code}`
        const text = `Unter folgendem Link können Sie Ihr Passwort zurücksetzen:

${link}

Alternativ können Sie auch den folgenden Code in das Eingabefeld kopieren:

${code}`

        console.log(text);

        sendmail(email,`[${SYSDATA.name}] Passwort zurücksetzen`, text);

      } else if ((tmp[0] as unknown as any).resetCode === resetCode) {
        this.service.update(client, tmp[0]._id, {$set: { passwort: await secureHash(newPassword) }, $unset: { resetCode: '' } });
        return true;
      }
    }

    return null;
  }

  @Mutation(() => Boolean, { nullable: false })
  async ChangePassword(
    @Context('client') client: Client,
    @Args('oldPassword', { nullable: false }) oldPassword: string,
    @Args('newPassword', { nullable: false }) newPassword: string,
  ): Promise<boolean> {
    if (!client.getUser()) {
      throw new HttpException('you need to be logged in to change your password!', 403);
    }

    const tmp = await this.service.findOneById(client.getUser()._id);

    if (!(await checkPassword(oldPassword, (tmp as unknown as any).passwort))) {
      throw new HttpException('old password wrong!', 403);
    }

    this.service.update(client, tmp._id, {$set: { passwort: await secureHash(newPassword) }});
    return true;
  }

  @Mutation(() => Person, { nullable: false })
  async PersonUpdate(
    @Context('client') client: Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID,
    @Args('givenName', { nullable: true }) givenName?: string,
    @Args('familyName', { nullable: true }) familyName?: string,
    @Args('email', { type: () => EmailAddress, nullable: true }) email?: EmailAddress,
    @Args('master', { nullable: true }) master?: boolean
  ): Promise<Person> {
    if (!client.isMaster() && !client.isSelf(id)) throw new HttpException('Access denied', 403);

    const tmp = await this.service.findOneById(id);

    const set: {
      givenName?: string
      familyName?: string
      email?: EmailAddress
      master?: boolean
    } = {};

    if (givenName !== undefined && givenName !== tmp.givenName) set.givenName = givenName
    if (familyName !== undefined && familyName !== tmp.familyName) set.familyName = familyName
    if (email !== undefined && email !== tmp.email) set.email = email
    if (client.isMaster() && master !== undefined && master !== tmp.master) set.master = master

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

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

  @Mutation(() => Person, { nullable: false })
  async PersonCreate(
    @Context('client') client: Client,
    @Args('givenName', { nullable: false }) givenName: string,
    @Args('familyName', { nullable: false }) familyName: string,
    @Args('email', { type: () => EmailAddress, nullable: false }) email: EmailAddress,
    @Args('master', { nullable: true }) master?: boolean
  ): Promise<Person> {
    if (!client.isMaster()) throw new HttpException('Access denied', 403);

    const neu = await this.service.insert(client, { givenName, familyName, email, master });

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

    return neu;
  }

  @Mutation(() => UUID, { nullable: false })
  async PersonDelete(
    @Context('client') client: Client,
    @Args('id', { type: () => UUID, nullable: false }) id: UUID
  ): Promise<UUID> {
    if (!client.isMaster()) throw new HttpException('Access denied', 403);

    this.service.delete(client, id);

    pubsub.publish('PersonDeleted', { PersonDeleted: id });

    return id;
  }
}
