import { DashboardsService } from '@app/event-emitter/dashboards/dashboards.service';
import { IssuesService } from '@app/event-emitter/issues/issues.service';
import { Dashboard, Widget } from '@app/event-emitter/models/dashboard';
import { Injectable, Logger } from '@nestjs/common';
import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron';
import { DailyEccmReportsV2Datasource } from 'src/couchdb-datasources/daily-eccm-reports-v2.datasource';
import { randomUUID } from 'crypto';
import { RedmineTypes } from '@app/event-emitter/models/redmine-types';
import { DateTime } from 'luxon';

export type Params = {
  query: any; // TODO: add type
  schedule: string;
};

export type Report = {
  id: string;
  name: string;
  datetime: number;
  datetimeFormatted: string;
  params: Params; // query?, schedule?, etc TODO
  data: any;
};

export type Job = {
  id: string;
  params: Params;
};

export const WIDGET_TYPE = 'daily_eccm_v2';

export const JOB_PREFIX = 'daily_eccm_v2';

export const UPDATE_RATE = 60 * 1000;

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

  private dashboardsService: DashboardsService;

  private previousAutoScanTime = 0;

  private cronJobs: Record<string, CronJob> = {};

  constructor(
    private schedulerRegistry: SchedulerRegistry,
    private issuesService: IssuesService,
  ) {}

  /**
   * Auto scan jobs every UPDATE_RATE seconds.
   * First call to autoScanJobs is done immediately.
   * Each subsequent call is done after the timeout.
   * The timeout is reset after each call to autoScanJobs.
   */
  initAutoScanJobs() {
    const tick = () => {
      setTimeout(tick, UPDATE_RATE);
      this.autoScanJobs();
    };
    tick();
  }

  async autoScanJobs() {
    this.logger.debug('Auto scan jobs started');
    const dbs = this.getDashboardsService();
    if (!dbs) {
      this.logger.warn('Dashboards service not initialized');
      this.logger.debug('Auto scan jobs finished');
      return;
    }
    const nowTime = new Date().getTime();
    const dashboards = await dbs.findDashboardsByWidgetType(
      WIDGET_TYPE,
      this.previousAutoScanTime,
    );
    this.previousAutoScanTime = nowTime;
    for (let i = 0; i < dashboards.length; i++) {
      const dashboard: Dashboard = dashboards[i];
      for (let j = 0; j < dashboard.data.widgets.length; j++) {
        const widget = dashboard.data.widgets[j];
        if (widget.type === WIDGET_TYPE) {
          const jobId = `${JOB_PREFIX}_${dashboard.id}_${widget.id}`;
          this.updateCronJob(jobId, widget, dashboard);
        }
      }
    }
    this.logger.debug('Auto scan jobs finished');
  }

  private updateCronJob(
    jobId: string,
    widget: Widget,
    dashboard: Dashboard,
  ): void {
    if (this.cronJobs[jobId]) {
      this.cronJobs[jobId].stop();
      this.schedulerRegistry.deleteCronJob(jobId);
    }
    const job = new CronJob(
      widget.dataLoaderParams?.schedule || '0 0 * * *',
      this.createJobHandler(jobId, dashboard, widget),
    );
    this.cronJobs[jobId] = job;
    this.schedulerRegistry.addCronJob(jobId, job);
    job.start();
  }

  getDashboardsService(): DashboardsService | null {
    if (!this.dashboardsService) {
      this.logger.warn('Dashboards service not initialized');
      return null;
    }
    return this.dashboardsService;
  }

  setDashboardsService(dashboardsService: DashboardsService) {
    this.dashboardsService = dashboardsService;
  }

  private createJobHandler(
    jobId: string,
    dashboard: Dashboard,
    widget: Widget,
  ): () => Promise<void> {
    this.logger.debug(
      `Create job handler for cron job ${jobId}, ` +
        `dashboard ${dashboard.id}, ` +
        `widget ${widget.id}`,
    );
    return async (): Promise<void> => {
      this.logger.debug(
        `Cron job ${jobId} started - dashboard ${dashboard.id}, widget ${widget.id}`,
      );

      const query = widget.dataLoaderParams?.query;
      if (!query) {
        this.logger.log(
          `Cron job ${jobId} finished - dashboard ${dashboard.id}, widget ${widget.id}`,
        );
        return;
      }

      let issues: RedmineTypes.Issue[] = [];
      try {
        issues = await this.issuesService.mergedTreesAndFind(query);
        await this.saveReport(dashboard, widget, issues);
      } catch (error) {
        this.logger.error(error);
      }

      this.logger.log(
        `Cron job ${jobId} finished - ` +
          `dashboard ${dashboard.id}, ` +
          `widget ${widget.id}, ` +
          `issues count ${issues.length}`,
      );
    };
  }

  private async saveReport(
    dashboard: Dashboard,
    widget: Widget,
    issues: RedmineTypes.Issue[],
  ): Promise<void> {
    const datasource = await DailyEccmReportsV2Datasource.getDatasource();
    const id = randomUUID();
    const dashboardId = dashboard.id;
    const widgetId = widget.id;
    const now = DateTime.now();
    const datetime = now.toMillis();
    const datetimeFormatted = now.toISO();
    const reportIssues = issues.map((issue: any) => {
      return {
        id: issue.id,
        subject: issue.subject,
        created_on: issue.created_on,
        updated_on: issue.updated_on,
        closed_on: issue.closed_on,
        status: issue.status,
        priority: issue.priority,
        author: issue.author,
        assigned_to: issue.assigned_to,
        dev: issue.dev,
        qa: issue.qa,
        cr: issue.cr,
        current_user: issue.current_user,
        tracker: issue.tracker,
        start_date: issue.start_date,
        due_date: issue.due_date,
        done_ratio: issue.done_ratio,
        estimated_hours: issue.estimated_hours,
        total_spent_hours: issue.total_spent_hours,
      };
    });
    const item: any = {
      _id: id,
      id: id,
      dashboardId,
      widgetId,
      datetime,
      datetimeFormatted,
      reportIssues,
    };
    await datasource.insert(item);
    this.logger.debug(
      `Report saved to db - id ${id}, ` +
        `issues count ${reportIssues.length}, ` +
        `datetime ${datetimeFormatted}`,
    );
  }
}
