import { Injectable, Logger } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { randomUUID } from 'crypto';
import { Interval } from 'date-fns';
import { Observable, combineLatest, from, map, switchMap, tap } from 'rxjs';

import { ExtractorsService } from '../feat-extractors/extractors.service';
import { LoadersService } from '../feat-loaders/loaders.service';
import { TransformersService } from '../feat-transformers/transformers.service';
import { ETLEventTypes } from './events/event-types.enum';
import { ExtractionDoneEvent } from './events/extraction-done.event';
import { LoadingDoneEvent } from './events/loading-done.event';
import { TransformationDoneEvent } from './events/transformation-done.event';
import { TourUpdateChecker } from './producer/tour-update';
import { SchichtplanungSynchronizer } from './producer/schichtplanung';

@Injectable()
export class ImporterService {
  private logger = new Logger(ImporterService.name);
  constructor(
    private readonly extractorsService: ExtractorsService,
    private readonly transformersService: TransformersService,
    private readonly loadersService: LoadersService,
    private readonly eventEmitter: EventEmitter2,
    private readonly tourUpdateChecker: TourUpdateChecker,
    private readonly schichtplanungSynchronizer: SchichtplanungSynchronizer,
  ) {}

  runTourUpdateCheck(interval: Interval) {
    return this.tourUpdateChecker.fetchTourUpdates(interval);
  }

  runSchichtplanungSync(day: Date) {
    return this.schichtplanungSynchronizer.syncSchichtplanung(day);
  }

  runImportForRange(interval: Interval) {
    const startTS = new Date().getTime();
    const jobId = randomUUID();
    return from(this.extractorsService.clearSource()).pipe(
      switchMap(() => this.runExtractorsForRange(interval, jobId)),
      tap(() =>
        this.logger.log(
          `Extraction of ${jobId} took ${new Date().getTime() - startTS}ms`,
        ),
      ),
      switchMap(() =>
        from(this.transformersService.clearStaging()).pipe(
          switchMap(() => this.runTransformersForRange(jobId)),
        ),
      ),
      tap(() =>
        this.logger.log(
          `Transformation of ${jobId} took ${new Date().getTime() - startTS}ms`,
        ),
      ),
      switchMap(() => this.runLoadersForRange(jobId)),
      tap(() =>
        this.logger.log(
          `Loading of ${jobId} took ${new Date().getTime() - startTS}ms`,
        ),
      ),
    );
  }

  private runExtractorsForRange(
    interval: Interval,
    jobId: string,
  ): Observable<void> {
    return combineLatest([
      this.extractorsService.extractAttendanceRegistrations(interval, jobId),
      this.extractorsService.extractEmployees(jobId),
      this.extractorsService.extractPatients(jobId),
      this.extractorsService.extractPlannedTimes(interval, jobId),
      this.extractorsService.extractTours(interval, jobId),
      this.extractorsService.extractWorkTimes(interval, jobId),
    ]).pipe(
      tap(() =>
        this.eventEmitter.emit(
          ETLEventTypes.ExtractionDone,
          new ExtractionDoneEvent(jobId),
        ),
      ),
      map(() => {
        return;
      }),
    );
  }

  private runTransformersForRange(jobId: string): Observable<void> {
    return combineLatest([
      this.transformersService.transformAttendanceRegistrations(jobId),
      this.transformersService.transformDyflexisEmployees(jobId),
      this.transformersService.transformDispoliveEmployees(jobId),
      this.transformersService.transformPatients(jobId),
      this.transformersService.transformPlannedTimes(jobId),
      this.transformersService.transformWorkTimes(jobId),
      this.transformersService.transformTours(jobId),
    ]).pipe(
      tap(() =>
        this.eventEmitter.emit(
          ETLEventTypes.TransformationDone,
          new TransformationDoneEvent(jobId),
        ),
      ),
      map(() => {
        return;
      }),
    );
  }

  private runLoadersForRange(jobId: string): Observable<void> {
    return this.loadersService.loadTours().pipe(
      switchMap(() => this.loadersService.loadPatients()),
      switchMap(() => this.loadersService.loadDyflexisEmployees()),
      switchMap(() => this.loadersService.loadDispoliveEmployees()),
      switchMap(() => this.loadersService.loadAttendanceRegistrations()),
      switchMap(() => this.loadersService.loadPlannedTimes()),
      switchMap(() => this.loadersService.loadWorkTimes()),
      tap(() =>
        this.eventEmitter.emit(
          ETLEventTypes.LoadingDone,
          new LoadingDoneEvent(jobId),
        ),
      ),
      map(() => {
        return;
      }),
    );
  }
}
