import { PlannedTime, Tour } from '@prisma/client';
import {
  areIntervalsOverlapping,
  endOfDay,
  format,
  Interval,
  isWithinInterval,
  startOfDay,
} from 'date-fns';
import { CalculationService } from 'src/modules/feat-calculation/calculation.service';
import { clampInterval, getHoursInInterval } from 'src/utils';

const EMPLOYEE_AMOUNT_BY_ORDINANCE_TYPE = {
  BTW: 1,
  TSW: 2,
  KTW: 2,
};

export class CarEntity {
  constructor(
    private readonly tours: Tour[],
    private readonly plannedTimes: PlannedTime[],
    public readonly carName: string,
    private readonly ordinanceType: string,
  ) {}

  availableTimeInInterval(interval: Interval): number {
    return (
      this.plannedTimes
        .map(({ startDate, endDate }) => ({
          start: new Date(startDate),
          end: new Date(endDate),
        }))
        .filter((plannedTimeInterval) =>
          areIntervalsOverlapping(plannedTimeInterval, interval),
        )
        .map((plannedTimeInterval) =>
          getHoursInInterval(plannedTimeInterval, interval),
        )
        .sort()
        .at(0) ?? 0
    );
  }

  tourAmountInInterval(interval: Interval): number {
    return this.toursInInterval(interval).reduce(
      (sum, { occupiedKm }) => (sum += occupiedKm > 30 ? occupiedKm / 30 : 1),
      0,
    );
  }

  toursInInterval(interval: Interval): Tour[] {
    return this.tours
      .filter(({ startDate }) =>
        isWithinInterval(new Date(startDate), interval),
      )
      .filter(({ occupiedKm }) => occupiedKm < 9999);
  }

  async breakEvenInInterval(
    interval: Interval,
    calculationService: CalculationService,
  ): Promise<{
    toursRevenue;
    personnelCosts;
    consumptionCost;
  }> {
    const toursInInterval = this.toursInInterval(interval);

    const toursRevenue = toursInInterval.reduce(
      (sum, tour) => (sum += tour.revenue),
      0,
    );

    const consumptionCost = toursInInterval.reduce(
      (sum, tour) => (sum += tour.consumptionCosts),
      0,
    );

    const plannedTimeIntervalInInterval =
      this.plannedTimeIntervalInInterval(interval);

    if (!plannedTimeIntervalInInterval) {
      return {
        toursRevenue,
        personnelCosts: 0,
        consumptionCost,
      };
    }

    const personnelCosts =
      calculationService.calculatePersonnelCostInInterval(
        clampInterval(plannedTimeIntervalInInterval, interval),
        this.employeeAmount,
        this.ordinanceType,
      ) / 100;
    return {
      toursRevenue,
      personnelCosts,
      consumptionCost,
    };
  }

  getTourAmountAtDateCreatedToday(date: Date): number {
    const dateInterval: Interval = {
      start: startOfDay(date),
      end: endOfDay(date),
    };

    const todayInterval: Interval = {
      start: startOfDay(new Date()),
      end: endOfDay(new Date()),
    };

    return this.tours.filter(
      ({ startDate, createdAt }) =>
        isWithinInterval(new Date(startDate), dateInterval) &&
        isWithinInterval(new Date(createdAt), todayInterval),
    ).length;
  }

  toString(): string {
    return `${this.carName} 
  ${this.tours.length} Touren
  ${this.plannedTimes.length} geplante Mitarbeiter
    ${
      this.plannedTimes.length
        ? this.plannedTimes
            .map(
              ({ startDate, endDate }) =>
                `${format(new Date(startDate), 'HH:mm')}-${format(
                  new Date(endDate),
                  'HH:mm',
                )}`,
            )
            .join(', ')
        : ''
    }`;
  }

  public plannedTimeIntervalInInterval(interval: Interval): Interval {
    return this.plannedTimes
      .map(({ startDate, endDate }) => ({
        start: new Date(startDate),
        end: new Date(endDate),
      }))
      .filter((plannedTimeInterval) =>
        areIntervalsOverlapping(plannedTimeInterval, interval),
      )
      .at(0);
  }

  private hasPlannedTimeInInterval(interval: Interval): boolean {
    return !!this.plannedTimeIntervalInInterval(interval);
  }

  private get employeeAmount(): number {
    return EMPLOYEE_AMOUNT_BY_ORDINANCE_TYPE[this.ordinanceType];
  }
}
