import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { addMilliseconds } from 'date-fns';
import { PubSub } from 'graphql-subscriptions';
import { generateIntervalForTimeRange } from 'src/utils';
import { CalculationService } from '../feat-calculation/calculation.service';
import { ETLEventTypes } from '../feat-etl/feat-importers/events/event-types.enum';
import { LoadingDoneEvent } from '../feat-etl/feat-importers/events/loading-done.event';
import { CarEntityService } from '../feat-business-objects/car-entity/car-entity.service';

@Injectable()
export class TimePerTourKpiService {
  public readonly pubSub = new PubSub();

  @OnEvent(ETLEventTypes.LoadingDone, { async: true })
  notifyOnPlannedTimeUpdate(event: LoadingDoneEvent): void {
    this.pubSub.publish('timePerTourUpdated', {
      timePerTourUpdated: { job: event.jobId },
    });
  }

  constructor(
    private readonly calculationService: CalculationService,
    private readonly carEntityService: CarEntityService,
  ) {}

  async toursPerTimeKPI(ordinanceType: string, date = new Date()) {
    const fullRangeInterval = generateIntervalForTimeRange(
      '05:00-01:00',
      'Europe/Berlin',
      date,
    );

    const subIntervals = [
      generateIntervalForTimeRange('05:00-12:00', 'Europe/Berlin'),
      generateIntervalForTimeRange('12:00-18:30', 'Europe/Berlin'),
      generateIntervalForTimeRange('18:30-01:00', 'Europe/Berlin'),
    ];

    const carEntities = await this.carEntityService.getCarsInInterval(
      fullRangeInterval,
      ordinanceType,
    );

    return Promise.all(
      subIntervals
        .map(({ start, end }, index, array) => ({
          start,
          end: index + 1 !== array.length ? addMilliseconds(end, -1) : end,
        }))
        .map(async (interval) => {
          const cars = await Promise.all(
            carEntities.map(async (carEntity) => {
              const tourAmount = carEntity.tourAmountInInterval(interval);
              const availableTime = carEntity.availableTimeInInterval(interval);

              const { personnelCosts, consumptionCost, toursRevenue } =
                await carEntity.breakEvenInInterval(
                  interval,
                  this.calculationService,
                );

              const baseValue = this.calculateKPIBaseValue(
                availableTime,
                tourAmount,
              );
              return {
                carName: carEntity.carName ?? 'Touren ohne Zuweisung',
                kpi: {
                  value: !carEntity.carName ? tourAmount : baseValue,
                  meta: {
                    tourAmount: Math.round(tourAmount * 100) / 100,
                    availableTime: Math.round(availableTime * 100) / 100,
                    baseValue,
                    breakEven:
                      Math.round(
                        (toursRevenue - personnelCosts - consumptionCost) * 100,
                      ) / 100,
                  },
                },
              };
            }),
          );

          const tourAmount = carEntities.reduce(
            (sum, carEntity) =>
              (sum += carEntity.tourAmountInInterval(interval)),
            0,
          );

          const availableTime = carEntities.reduce(
            (sum, carEntity) =>
              (sum += carEntity.availableTimeInInterval(interval)),
            0,
          );

          let revenue = 0;
          let personnelCost = 0;
          let consumptionCost = 0;
          for (const carEntity of carEntities) {
            const {
              personnelCosts,
              consumptionCost: cc,
              toursRevenue,
            } = await carEntity.breakEvenInInterval(
              interval,
              this.calculationService,
            );
            revenue += toursRevenue;
            personnelCost += personnelCosts;
            consumptionCost += cc;
          }
          const baseValue = this.calculateKPIBaseValue(
            availableTime,
            tourAmount,
          );
          return {
            interval,
            kpi: {
              value: baseValue,
              meta: {
                tourAmount: Math.round(tourAmount * 100) / 100,
                availableTime: Math.round(availableTime * 100) / 100,
                baseValue,
                breakEven:
                  Math.round(
                    (revenue - personnelCost - consumptionCost) * 100,
                  ) / 100,
              },
            },
            cars,
          };
        }),
    );
  }

  calculateKPIBaseValue(availableTime: number, tourAmount: number): number {
    if (tourAmount === 0 && availableTime > 0) {
      return Math.round((availableTime / 0.01) * 1000) / 1000;
    } else if (tourAmount === 0) {
      return -1;
    }
    return Math.round((availableTime / tourAmount) * 1000) / 1000;
  }
}
