import { Injectable, Logger } from '@nestjs/common';
import { Cron, SchedulerRegistry } from '@nestjs/schedule';
import {
  Interval as TimeInterval,
  addDays,
  eachDayOfInterval,
  endOfDay,
  format,
  set,
  startOfDay,
} from 'date-fns';
import { concatMap, from, lastValueFrom, switchMap, take, tap } from 'rxjs';
import { ImporterService } from './modules/feat-etl/feat-importers/importer.service';
import { TicketService } from './modules/feat-tickets/tickets.service';
import { chunk } from './utils';
import { AnomaliesService } from './modules/feat-anomalies/anomalies.service';

@Injectable()
export class AppService {
  private readonly logger = new Logger(AppService.name);

  constructor(
    private readonly importer: ImporterService,
    private readonly ticketService: TicketService,
    private readonly anomalieService: AnomaliesService,
    private readonly schedulerRegistry: SchedulerRegistry,
  ) {}

  // @Cron('0 */5 2-20 * * *')
  async fetchDataContinously() {
    const now = new Date();
    const fetchInterval = {
      start: startOfDay(addDays(now, -14)),
      end: endOfDay(addDays(now, 7)),
    };
    this.importer
      .runImportForRange(fetchInterval)
      .pipe(
        take(1),
        tap(() =>
          this.logger.log(
            `import took ${(Date.now() - now.getTime()) / 1000}s`,
          ),
        ),
        switchMap(() => from(this.ticketService.createTickets(fetchInterval))),
        switchMap(() =>
          from(
            this.anomalieService
              .detectAnomalies(fetchInterval)
              .then(() => this.anomalieService.validateAnomalies()),
          ),
        ),
      )
      .subscribe();
  }

  // @Interval(5 * 60 * 1000)
  async fetchDataInIntervals() {
    const timeouts = this.schedulerRegistry.getTimeouts();
    this.logger.log(`Timeouts: ${timeouts.join(', ')}`);
    if (timeouts.length) return;

    const now = new Date();
    const fetchInterval: TimeInterval = {
      start: set(new Date(), {
        year: now.getFullYear(),
        month: 0,
        date: 1,
      }),
      end: endOfDay(now),
    };

    const intervals = [
      ...this.splitIntervalIntoPeriods(fetchInterval, 14),
    ].reverse();

    intervals.forEach((interval, index) => {
      const readableInterval = `${format(
        new Date(interval.start),
        'yyyy-MM-dd',
      )} - ${format(new Date(interval.end), 'yyyy-MM-dd')}`;
      const timeoutName = `Year: ${readableInterval}`;
      const callback = () => {
        this.logger.log(`Timeout "${timeoutName}"`);
        from([interval])
          .pipe(
            concatMap((interval) => this.importer.runImportForRange(interval)),
            switchMap(() => from(this.ticketService.createTickets(interval))),
          )
          .subscribe();
      };
      this.addTimeout(timeoutName, 60 * 1000 * index + 60 * 1000, callback);
    });
  }

  // @Cron('0 2,7,12,17,22,27,32,37,42,47,52,57 2-20 * * *')
  async fetchDataSixWeeks() {
    const now = new Date();
    const fetchInterval: TimeInterval = {
      start: startOfDay(addDays(now, -90)),
      end: endOfDay(now),
    };

    const intervals = this.splitIntervalIntoPeriods(fetchInterval, 14);

    await lastValueFrom(
      from(intervals).pipe(
        concatMap((interval, index) =>
          this.importer
            .runImportForRange(interval)
            .pipe(
              tap(() =>
                this.logger.log(
                  `${index + 1}. import three months took ${
                    (Date.now() - now.getTime()) / 1000
                  }s`,
                ),
              ),
            ),
        ),
      ),
    );
    // await this.ticketService.createTickets(fetchInterval);
  }

  // @Cron('* * 0 * * *')
  async fetchDataYearly() {
    const now = new Date();
    const fetchInterval: TimeInterval = {
      start: set(new Date(), {
        year: now.getFullYear(),
        month: 0,
        date: 1,
      }),
      end: endOfDay(now),
    };

    const intervals = this.splitIntervalIntoPeriods(fetchInterval);

    from(intervals)
      .pipe(
        concatMap((interval, index) =>
          this.importer
            .runImportForRange(interval)
            .pipe(
              tap(() =>
                this.logger.log(
                  `${index + 1}. import year took ${
                    (Date.now() - now.getTime()) / 1000
                  }s`,
                ),
              ),
            ),
        ),
        switchMap(() => from(this.ticketService.createTickets(fetchInterval))),
      )
      .subscribe();
  }

  private splitIntervalIntoPeriods(
    interval: TimeInterval,
    chunkSize = 30,
  ): TimeInterval[] {
    return chunk(eachDayOfInterval(interval), chunkSize).map((dates) => ({
      start: startOfDay(dates.at(0) as Date),
      end: endOfDay(dates.at(-1) as Date),
    }));
  }

  private addTimeout(
    name: string,
    milliseconds: number,
    callback: () => void,
  ): void {
    const timeout = setTimeout(callback, milliseconds);
    this.schedulerRegistry.addTimeout(name, timeout);
  }
}
