import { Injectable } from '@nestjs/common';
import { ApprovalState, Prisma, Tour } from '@prisma/client';
import { addDays, endOfDay, format, startOfDay, subDays } from 'date-fns';
import { PrismaService } from 'src/core/database/prisma.service';
import { AnomalyTypes } from '../feat-business-objects/entities/anomaly/anomaly-types.enum';
import { AnomalyService } from '../feat-business-objects/entities/anomaly/anomaly.service';
import {
  HomeDashboardKPIsObjectType,
  RevenueTrendObjectType,
} from './home.object-type';

@Injectable()
export class HomeService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly anomalyService: AnomalyService,
  ) {}

  async homeDashboardKpis(): Promise<HomeDashboardKPIsObjectType> {
    const anomalyCount = await this.getAnomalyCount();
    const employeesWithoutTourCount = await this.getEmployeesWithoutTourCount();
    const toursWithoutApprovalCount = await this.getToursWithoutApprovalCount();
    const billableTourCount = await this.getBillableTourCount();
    const billableTourRevenueSum = await this.getBillableTourRevenueSum();
    const {
      tourCount: billedTourCount,
      positiveRevenueDeviation: billedTourPositiveRevenueDeviation,
      negativeRevenueDeviation: billedTourNegativeRevenueDeviation,
    } = await this.getBilledTourKPIs();
    const revenueTrend = await this.getRevenueTrend();

    return {
      anomalyCount,
      employeesWithoutTourCount,
      toursWithoutApprovalCount,
      billableTourCount,
      billableTourRevenueSum,
      billedTourCount,
      billedTourPositiveRevenueDeviation,
      billedTourNegativeRevenueDeviation,
      revenueTrend,
    };
  }

  private async getAnomalyCount(): Promise<number> {
    return await this.anomalyService.countVisibleAnomalies();
  }

  private async getEmployeesWithoutTourCount(): Promise<number> {
    return await this.anomalyService.countVisibleAnomalies([
      AnomalyTypes.WorkingEmployeeNotInDistributionSystem,
    ]);
  }

  private async getToursWithoutApprovalCount(): Promise<number> {
    return await this.prisma.tour.count({
      where: {
        startDate: {
          gte: startOfDay(addDays(new Date(), 3)),
        },
        OR: [
          {
            Ticket: {
              approvalState: ApprovalState.UNKNOWN,
            },
          },
          {
            Ticket: {
              approvalState: ApprovalState.REQUIRED,
            },
          },
        ],
      },
    });
  }

  private async getBillableTourCount(): Promise<number> {
    return await this.prisma.tour.count({
      where: {
        done: true,
        check: 0,
        empty: { not: null },
        ticketId: { not: null },
        occupiedKm: {
          lt: 9000,
        },
        AND: [
          {
            OR: [
              {
                carName: null,
              },
              {
                AND: [
                  {
                    carName: {
                      not: 'Storno',
                    },
                  },
                  {
                    carName: {
                      not: 'Löschen',
                    },
                  },
                  {
                    carName: {
                      not: 'Abgabe',
                    },
                  },
                ],
              },
            ],
          },
          {
            deletedAt: null,
          },
          {
            OR: [
              { patientId: null },
              {
                AND: [
                  {
                    patientId: {
                      // Pause
                      not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
                    },
                  },
                  {
                    patientId: {
                      // Feierabend
                      not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
                    },
                  },
                ],
              },
            ],
          },
        ],
      },
    });
  }

  private async getBillableTourRevenueSum(): Promise<number> {
    const aggr = await this.prisma.tour.aggregate({
      _sum: {
        revenue: true,
      },
      where: {
        done: true,
        check: 0,
        empty: { not: null },
        ticketId: { not: null },
        occupiedKm: {
          lt: 9000,
        },
        AND: [
          {
            OR: [
              {
                carName: null,
              },
              {
                AND: [
                  {
                    carName: {
                      not: 'Storno',
                    },
                  },
                  {
                    carName: {
                      not: 'Löschen',
                    },
                  },
                  {
                    carName: {
                      not: 'Abgabe',
                    },
                  },
                ],
              },
            ],
          },
          {
            deletedAt: null,
          },
          {
            OR: [
              { patientId: null },
              {
                AND: [
                  {
                    patientId: {
                      // Pause
                      not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
                    },
                  },
                  {
                    patientId: {
                      // Feierabend
                      not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
                    },
                  },
                ],
              },
            ],
          },
        ],
      },
    });
    return aggr._sum.revenue;
  }

  private async getBilledTourKPIs(): Promise<{
    tourCount: number;
    positiveRevenueDeviation: number;
    negativeRevenueDeviation: number;
  }> {
    const toursWithCurrentState: Prisma.TourWhereInput = {
      check: 2,
      revenueDeviation: { not: 0 },
      empty: {
        not: null,
      },
      ticketId: {
        not: null,
      },
      occupiedKm: {
        lt: 9000,
      },
      AND: [
        {
          OR: [
            {
              carName: null,
            },
            {
              AND: [
                {
                  carName: {
                    not: 'Storno',
                  },
                },
                {
                  carName: {
                    not: 'Löschen',
                  },
                },
                {
                  carName: {
                    not: 'Abgabe',
                  },
                },
              ],
            },
          ],
        },
        {
          deletedAt: null,
        },
        {
          OR: [
            { patientId: null },
            {
              AND: [
                {
                  patientId: {
                    // Pause
                    not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
                  },
                },
                {
                  patientId: {
                    // Feierabend
                    not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
                  },
                },
              ],
            },
          ],
        },
      ],
    };

    const [tourCount, positiveRevenueDeviation, negativeRevenueDeviation] =
      await this.prisma.$transaction([
        this.prisma.tour.count({
          where: { ...toursWithCurrentState },
        }),
        this.prisma.tour.aggregate({
          _sum: {
            revenueDeviation: true,
          },
          where: {
            ...toursWithCurrentState,
            revenueDeviation: {
              gt: 0,
            },
          },
        }),
        this.prisma.tour.aggregate({
          _sum: {
            revenueDeviation: true,
          },
          where: {
            ...toursWithCurrentState,
            revenueDeviation: {
              lt: 0,
            },
          },
        }),
      ]);

    return {
      tourCount,
      positiveRevenueDeviation:
        positiveRevenueDeviation._sum.revenueDeviation ?? 0,
      negativeRevenueDeviation:
        negativeRevenueDeviation._sum.revenueDeviation ?? 0,
    };
  }

  private async getRevenueTrend(): Promise<RevenueTrendObjectType[]> {
    const tours = await this.getTours();

    const toursPerDay = tours.reduce(
      (acc, tour) => {
        const tourDay = format(new Date(tour.startDate), 'yyyy-MM-dd');

        return {
          ...acc,
          [tourDay]: [...(acc[tourDay]?.length ? acc[tourDay] : []), tour],
        };
      },
      {} as Record<string, Tour[]>,
    ) as Record<string, Tour[]>;

    const revenueTrend = Object.entries(toursPerDay).flatMap(([day, tours]) => {
      const toursByOrdinanceType = tours.reduce(
        (acc, tour) => {
          return {
            ...acc,
            [tour.ordinanceType]: [
              ...(acc[tour.ordinanceType] ? acc[tour.ordinanceType] : []),
              tour,
            ],
          };
        },
        {} as Record<string, Tour[]>,
      );
      return Object.entries(toursByOrdinanceType).map(([ot, tours]) => {
        const revenueSum = tours.reduce(
          (sum, { revenue }) => (sum += revenue),
          0,
        );
        return {
          revenueSum,
          date: new Date(day),
          ordinanceType: ot,
        };
      });
    });

    return revenueTrend;
  }

  private getTours(): Promise<Tour[]> {
    return this.prisma.tour.findMany({
      where: {
        AND: [
          {
            startDate: {
              gte: startOfDay(subDays(new Date(), 10)),
              lte: endOfDay(new Date()),
            },
          },
          {
            AND: [
              { ordinanceType: { not: null } },
              { ordinanceType: { not: 'Tragehilfe' } },
            ],
          },
          {
            OR: [
              {
                carName: null,
              },
              {
                AND: [
                  {
                    carName: {
                      not: 'Storno',
                    },
                  },
                  {
                    carName: {
                      not: 'Löschen',
                    },
                  },
                  {
                    carName: {
                      not: 'Abgabe',
                    },
                  },
                ],
              },
            ],
          },
          {
            check: {
              not: 3,
            },
          },
          {
            occupiedKm: {
              lt: 9999,
            },
          },
          {
            OR: [
              {
                patientId: null,
              },
              {
                patientId: {
                  // Pause
                  not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
                },
              },
              {
                patientId: {
                  // Feierabend
                  not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
                },
              },
            ],
          },
        ],
      },
    });
  }
}
