import { Logger } from '@nestjs/common';
import { Anomaly, Prisma } from '@prisma/client';
import { Interval, format } from 'date-fns';
import { toZonedTime } from 'date-fns-tz';

import { AnomalyTypes } from 'src/modules/feat-business-objects/entities/anomaly/anomaly-types.enum';
import { AnomalyDetector } from './anomaly-detector.class';

export class DriverCertificateDetector extends AnomalyDetector {
  private readonly logger = new Logger(DriverCertificateDetector.name);
  readonly type = AnomalyTypes.DriverCertificate;

  async detect(inInterval: Interval): Promise<Prisma.AnomalyCreateInput[]> {
    const toursInRange = await this.dataService.findToursInInterval(inInterval);

    const anomalies = await Promise.all(
      toursInRange
        .filter(({ driverId }) => !!driverId)
        .map(
          async ({
            id: tourId,
            driverId,
            startDate,
            driverName,
            carName,
            operationId,
          }) => {
            try {
              const driverEmployee =
                await this.dataService.findEmployeeById(driverId);
              if (!driverEmployee) {
                return;
                return this.createAnomaly(
                  `${operationId}-${driverId}-${this.type}`,
                  operationId,
                  carName,
                  driverName ?? '<Fahrer gelöscht>',
                  new Date(startDate),
                );
              }
              if (
                !this.isPersonTransportCertificateValidAtStartOfTour(
                  driverEmployee.personTransportCertificate,
                  startDate,
                )
              ) {
                this.addAssociatedEntity('employee', driverEmployee.id);
                this.addAssociatedEntity('tour', tourId);

                return this.createAnomaly(
                  `${operationId}-${driverId}-${this.type}`,
                  operationId,
                  carName,
                  driverName,
                  new Date(startDate),
                );
              }
            } catch (error) {
              this.logger.error(error);
              return;
            }
          },
        ),
    );
    return anomalies.filter((anomaly) => !!anomaly);
  }

  async validate(anomaly: Anomaly): Promise<Anomaly> {
    const [operationId] = anomaly.id.split('-');
    try {
      const tour = await this.dataService.findTourByOperationId(operationId);
      if (!tour.driverId) {
        return { ...anomaly };
      }
      const employee = await this.dataService.findEmployeeById(tour.driverId);
      if (!employee) {
        return { ...anomaly };
      }
      if (
        this.isPersonTransportCertificateValidAtStartOfTour(
          employee.personTransportCertificate,
          tour.startDate,
        )
      ) {
        return {
          ...anomaly,
          resolvedAt: new Date(),
        };
      } else {
        return {
          ...anomaly,
        };
      }
    } catch (error) {
      this.logger.error(error, { anomalyId: anomaly.id });
      return anomaly;
    }
  }

  createDescription({
    name,
    carName,
  }: {
    name: string;
    carName: string;
  }): string {
    return `${carName}: ${name} hat keinen gültigen P-Schein`;
  }

  createSolution({
    carName,
    operationId,
    startDate,
  }: {
    carName: string;
    operationId: string;
    startDate: Date;
  }): Prisma.SolutionTargetCreateInput[] {
    return [
      {
        name: `Fahrzeug ${carName}`,
        steps: {
          create: [
            {
              description: `Klären Sie ob im Fahrzeug ein Fahrer mit P-Schein sitzt`,
            },
            {
              description: `Informieren Sie das Team des Fahrzeugs, dass sie sich neu anmelden müssen`,
            },
          ],
        },
      },
      {
        name: 'DispoLive',
        steps: {
          create: [
            {
              description: `Passen Sie den Fahrer und Beifahrer der Tour ${operationId} an. Startzeit: ${format(
                startDate,
                'HH:mm dd.MM.yy',
              )}`,
            },
          ],
        },
      },
    ];
  }

  private createAnomaly(
    anomalyId: string,
    operationId: string,
    carName: string,
    name: string,
    startDate: Date,
  ): Prisma.AnomalyCreateInput {
    return {
      ...this.getAnomalySkeleton(),
      id: anomalyId,
      type: this.type,
      groupKey: `${format(
        new Date(startDate),
        'yyyy-MM-dd-HH-mm',
      )}-${operationId}`,
      groupDescription: `${operationId} - ${format(
        toZonedTime(new Date(startDate), 'Europe/Berlin'),
        'HH:mm dd.MM.yy',
      )}`,
      solution: {
        create: this.createSolution({ carName, operationId, startDate }),
      },
      description: this.createDescription({ name, carName }),
    };
  }

  private isPersonTransportCertificateValidAtStartOfTour(
    personTransportCertificateValidUntil: Date,
    startDateOfTour: Date,
  ): boolean {
    return (
      new Date(personTransportCertificateValidUntil).getTime() >=
      new Date(startDateOfTour).getTime()
    );
  }
}
