import { Injectable } from '@nestjs/common';
import {
  ApprovalState,
  Prisma,
  TicketValidationState,
  Tour,
} from '@prisma/client';
import {
  add,
  differenceInHours,
  differenceInMinutes,
  set,
  subDays,
  subHours,
  subWeeks
} from 'date-fns';
import { PrismaService } from 'src/core/database/prisma.service';
import { generateIntervalForTimeRange } from 'src/utils';
import { CarEntityService } from '../feat-business-objects/car-entity/car-entity.service';
import { CalculationService } from '../feat-calculation/calculation.service';
import { KpiInfoObjectType, MetricObjectType } from './object-types';

function ticketWithCurrentStateAndTours(
  ticketState: TicketValidationState,
): Prisma.TicketWhereInput {
  return { currentState: ticketState, tours: { some: { check: 0 } } };
}

function dateFromTime(timeString: string): Date {
  const [hours, minutes] = timeString.split(':');
  return set(new Date(), {
    hours: parseInt(hours),
    minutes: parseInt(minutes),
    seconds: 0,
    milliseconds: 0,
  });
}

@Injectable()
export class KpiService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly carEntity: CarEntityService,
    private readonly calculationService: CalculationService,
  ) {}

  async calcCityTourAvgTime(ordinanceType: string) {
    const tourTimestamps = await this.prisma.tour.findMany({
      select: {
        startBegin: true,
        startEnd: true,
        target: true,
        targetBegin: true,
        empty: true,
      },
      where: {
        ...(ordinanceType !== 'ALL'
          ? {
              ordinanceType: {
                contains: ordinanceType,
              },
            }
          : {}),
        done: true,
        totalKm: {
          lte: 17,
        },
      },
      take: 5000,
    });
    const tourTimes = tourTimestamps
      .filter((timings) => Object.values(timings).filter(Boolean).length > 0)
      .map((timings) => {
        const times = Object.values(timings).filter(Boolean).sort();
        const startTime = times.at(0);
        const endTime = times.at(-1);
        return differenceInMinutes(
          dateFromTime(endTime),
          dateFromTime(startTime),
        );
      });
    return tourTimes.reduce((sum, cur) => (sum += cur), 0) / tourTimes.length;
  }

  async calcCityTourAvgRevenue(ordinanceType: string) {
    const tourRevenues = await this.prisma.tour.findMany({
      select: {
        revenue: true,
      },
      where: {
        ...(ordinanceType !== 'ALL'
          ? {
              ordinanceType: {
                contains: ordinanceType,
              },
            }
          : {}),
        done: true,
        totalKm: {
          lte: 17,
        },
      },
      take: 5000,
    });
    return (
      tourRevenues.reduce((sum, { revenue }) => (sum += revenue), 0) /
      tourRevenues.length
    );
  }

  upcomingTours(): Promise<Tour[]> {
    const now = new Date();
    const endUpcoming = add(now, { minutes: 20 });
    return this.prisma.tour.findMany({
      where: {
        AND: [
          {
            startDate: {
              gte: subHours(now, 4),
            },
          },
          {
            startDate: {
              lt: endUpcoming,
            },
          },
          {
            startBegin: null,
          },
          {
            OR: [
              {
                carName: null,
              },
              {
                AND: [
                  {
                    carName: {
                      not: 'Storno',
                    },
                  },
                  {
                    carName: {
                      not: 'Löschen',
                    },
                  },
                  {
                    carName: {
                      not: 'Abgabe',
                    },
                  },
                ],
              },
            ],
          },
          {
            AND: [
              {
                patientId: {
                  // Pause
                  not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
                },
              },
              {
                patientId: {
                  // Feierabend
                  not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
                },
              },
            ],
          },
          {
            deletedAt: null,
          },
        ],
      },
    });
  }

  async managerKpi(ordinanceType: string): Promise<KpiInfoObjectType> {
    const avgRevenueCityTour = await this.calcCityTourAvgRevenue(ordinanceType);
    const avgCityTourTimeInMinutes =
      await this.calcCityTourAvgTime(ordinanceType);
    const forDay = subDays(new Date(), 0);
    const cars = await this.carEntity.getCarsForOperationDay(
      forDay,
      ordinanceType,
    );
    const workdayInterval = generateIntervalForTimeRange(
      '05:00-04:59',
      'Europe/Berlin',
      forDay,
    );

    // console.log(cars.map(car => ))

    const workCapacity = cars
      .map((car) => car.availableTimeInInterval(workdayInterval))
      .reduce((sum, cur) => (sum += cur), 0);

    const tourAmount = cars
      .map((car) => car.tourAmountInInterval(workdayInterval))
      .reduce((sum, cur) => (sum += cur), 0);

    const possibleRevenue =
      ((workCapacity * 60) / avgCityTourTimeInMinutes) * avgRevenueCityTour;
    const carRevenues = await Promise.all([
      ...cars.map((car) =>
        car.breakEvenInInterval(workdayInterval, this.calculationService),
      ),
    ]);
    const totalRevenue = carRevenues.reduce(
      (sum, { toursRevenue }) => (sum += toursRevenue),
      0,
    );

    return {
      main: {
        label: 'Realisierbarer Umsatz',
        value: Math.floor(
          (100 / (possibleRevenue == 0 ? 1 : possibleRevenue)) * totalRevenue,
        ),
      },
      secondary: {
        label: 'Realisierbare Touren',
        value:
          possibleRevenue > totalRevenue
            ? (possibleRevenue - totalRevenue) / avgRevenueCityTour
            : 0,
      },
      additional: [
        {
          label: 'Anzahl Touren',
          value: tourAmount,
        },
        {
          label: 'Kapazität (in h)',
          value: workCapacity,
        },
        {
          label: '⌀ Zeit Stadttour (in min)',
          value: avgCityTourTimeInMinutes,
        },
        {
          label: '⌀ Umsatz Stadttour',
          value: avgRevenueCityTour,
        },
      ],
    };
  }

  async ticketStateKpi(
    ticketState: TicketValidationState,
  ): Promise<KpiInfoObjectType> {
    return {
      main: await this.fastMovingTicketPercentMetric(ticketState),
      secondary: await this.averageTimePerStateMetric(ticketState),
      additional: [
        await this.ticketCountPerStateMetric(ticketState),
        ...(ticketState === TicketValidationState.USER_INTERACTION_REQUIRED
          ? [await this.ticketApprovalRequiredCountMetric(ticketState)]
          : []),
        await this.tourCountPerTicketStateMetric(ticketState),
      ],
    };
  }

  private async fastMovingTicketPercentMetric(
    ticketState: TicketValidationState,
  ): Promise<MetricObjectType> {
    const ticketsGotState24HoursAgo = await this.prisma.ticket.findMany({
      where: {
        state: {
          some: {
            state: ticketState,
            createdAt: {
              gte: subHours(new Date(), 24),
            },
          },
        },
      },
    });

    const ticketsMoved = ticketsGotState24HoursAgo.filter(
      ({ currentState }) => currentState !== ticketState,
    );

    return {
      label: 'Neue Tickets bearbeitet innerhalb von 24 Stunden',
      value:
        ticketsMoved.length === 0
          ? 0
          : Math.floor(
              (100 / ticketsGotState24HoursAgo.length) * ticketsMoved.length,
            ),
    };
  }

  private async averageTimePerStateMetric(
    ticketState: TicketValidationState,
  ): Promise<MetricObjectType> {
    const movedTickets = await this.prisma.ticket.findMany({
      where: {
        currentState: {
          not: ticketState,
        },
        state: {
          some: {
            state: ticketState,
          },
        },
        createdAt: {
          gte: subWeeks(new Date(), 2),
        },
      },
      include: {
        state: true,
      },
      orderBy: {
        id: 'asc'
      },
      take: 1000
    });
    const timePerTickets = movedTickets
      .map(({ state }) => {
        const statesSorted = [...state].sort(
          (a, b) =>
            new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
        );
        const currentStateIdx = statesSorted.findIndex(
          (s) => s.state === ticketState,
        );
        if (currentStateIdx === -1) return;
        // if (statesSorted.length - 1 === currentStateIdx) return;
        return differenceInHours(
          new Date(statesSorted.at(currentStateIdx + 1).createdAt),
          new Date(statesSorted.at(currentStateIdx).createdAt),
        );
      })
      .filter((v) => !!v);
    if (timePerTickets.length === 0) {
      return { label: '⌀ Verweildauer (in h)', value: 0 };
    }
    return {
      label: '⌀ Verweildauer (in h)',
      value:
        timePerTickets.reduce((sum, cur) => (sum += cur), 0) /
        timePerTickets.length,
    };
  }

  private async ticketCountPerStateMetric(
    ticketState: TicketValidationState,
  ): Promise<MetricObjectType> {
    const count = await this.prisma.ticket.count({
      where: { ...ticketWithCurrentStateAndTours(ticketState) },
    });
    return { label: 'Tickets', value: count };
  }

  private async ticketApprovalRequiredCountMetric(
    ticketState: TicketValidationState,
  ): Promise<MetricObjectType> {
    const count = await this.prisma.ticket.count({
      where: {
        ...ticketWithCurrentStateAndTours(ticketState),
        approvalState: ApprovalState.REQUIRED,
      },
    });
    return { label: 'Genehmigungspflicht', value: count };
  }

  private async tourCountPerTicketStateMetric(
    ticketState: TicketValidationState,
  ): Promise<MetricObjectType> {
    const count = await this.prisma.tour.count({
      where: {
        Ticket: {
          ...ticketWithCurrentStateAndTours(ticketState),
        },
      },
    });
    return {
      label: 'Touren',
      value: count,
    };
  }
}
