import { InjectQueue } from '@nestjs/bullmq';
import { Injectable } from '@nestjs/common';
import { Queue } from 'bullmq';
import { addMinutes, endOfDay, getMonth, getYear, isSameDay, startOfDay } from 'date-fns';
import {
  lastValueFrom
} from 'rxjs';
import { EmployeeService } from 'src/modules/feat-business-objects/entities/employee/employee.service';
import { DispoLiveApiExtractorsService } from '../../feat-extractors/dispo-live-api-extractors.service';
import { DyflexisExtractorsService } from '../../feat-extractors/dyflexis-extractors.service';
import {
  parseCETDateTime,
  pick,
  transformProperties,
} from '../../feat-transformers/transformer.functions';
import { SCHICHTPLANUNG_QUEUE } from '../tokens';

@Injectable()
export class SchichtplanungSynchronizer {
  constructor(
    private readonly dlApiService: DispoLiveApiExtractorsService,
    private readonly dyflexisService: DyflexisExtractorsService,
    private readonly employeeService: EmployeeService,
    @InjectQueue(SCHICHTPLANUNG_QUEUE)
    private readonly schichtplanungQueue: Queue,
  ) {}

  // TODO: Do we really want to load everything fresh? Anwer: For now it's fine, by this we circumvent problems arising from our application code
  async syncSchichtplanung(day: Date) {
    // Fetch and prepare data
    const plannedTimes = await lastValueFrom(
      this.dyflexisService.fetchPlannedTimeForMonth(
        getMonth(day) + 1,
        getYear(day),
      ),
    );
    const plannedTimeForDay = plannedTimes
      .filter(({ mark }) => mark !== 'sick')
      .filter(({ deleted }) => deleted != '1')
      .filter(({ user_id }) => user_id != '0')
      .filter(
        ({ department_name }) =>
          department_name !== 'Büro' && department_name !== 'Leitstelle',
      )
      .filter(({ start_date }) => isSameDay(new Date(start_date), day));

    const cars = await lastValueFrom(this.dlApiService.fahrzeuge());

    // Create Schichtplanung
    const schichtplanung = [];
    for (const plannedTime of plannedTimeForDay) {
      const { start_date, end_date } = transformProperties(
        parseCETDateTime('yyyy-MM-dd HH:mm:ss'),
      )(
        pick(['start_date', 'end_date'])(
          plannedTime as Record<'start_date' | 'end_date', string>,
        ),
      );

      const employee = await this.employeeService.findByPersonnelNumber(
        plannedTime.user_id,
      );
      if (!employee) {
        console.error(`couldn\'t find employee with id ${plannedTime.user_id}`);
        continue;
      }

      const car = cars.data.find(({ kfzNr }) =>
        this.isCarNameForDepartment(plannedTime.department_name, kfzNr),
      );
      if (!car) {
        console.error(
          `couldn\'t find car with department ${plannedTime.department_name}`,
        );
        continue;
      }

      let planning = {
        className: 'greenItem',
        start: start_date.toISOString(),
        end: end_date.toISOString(),
        group: car._id,
        mitarbeiterId: employee.id,
      }
      
      if(car.kfzNr.indexOf("KTW") > -1) {
        planning = {
          ...planning,
          start: addMinutes(new Date(planning.start), 15).toISOString(),
        }
      }

      schichtplanung.push(planning);
    }

    // Determine update set
    const currentPlanung = await lastValueFrom(
      this.dlApiService.fahrzeugplanungFindByRange({
        start: startOfDay(day),
        end: endOfDay(day),
      }),
    ).then(({ data }) =>
      data.filter(({ className }) => className === 'greenItem'),
    );

    const toCreate = [];
    const toUpdate = [];
    const toDelete = currentPlanung
      .filter(
        ({ mitarbeiterId }) =>
          schichtplanung.findIndex(
            ({ mitarbeiterId: id }) => id === mitarbeiterId,
          ) === -1,
      )
      .map(({ _id }) => ({ _id }));

    for (const plan of schichtplanung) {
      const plannedTime = currentPlanung.find(
        ({ mitarbeiterId }) => mitarbeiterId === plan.mitarbeiterId,
      );
      if (!plannedTime) {
        toCreate.push(plan);
        continue;
      }
      if (
        !!plannedTime &&
        (plannedTime?.start !== plan.start || plannedTime?.end !== plan.end || plannedTime?.group !== plan.group)
      ) {
        toUpdate.push({ ...plan, _id: plannedTime._id });
      }
    }

    await Promise.all([
      this.schichtplanungQueue.addBulk(
        toCreate.map((item) => ({
          name: 'Create',
          data: item,
        })),
      ),
      this.schichtplanungQueue.addBulk(
        toUpdate.map((item) => ({
          name: 'Update',
          data: item,
        })),
      ),
      this.schichtplanungQueue.addBulk(
        toDelete.map((item) => ({
          name: 'Delete',
          data: item._id,
        })),
      ),
    ]);

    return {
      create: toCreate,
      update: toUpdate,
      delete: toDelete,
    };
  }

  private isCarNameForDepartment(department: string, carName: string): boolean {
    if (!department || !carName) return false;
    const [ordinanceType, carNumber] = department
      .replace('1/', '')
      .replace('E', '')
      .split('-')
      .map((part) => part.trim());
    return (
      (carName.indexOf(ordinanceType) > -1 &&
        carName.indexOf(carNumber) > -1) ||
      carName === department
    );
  }
}
