import { Injectable, Logger } from '@nestjs/common';
import { EventsListener } from '../events/events-listener';
import { BehaviorSubject } from 'rxjs';
import { SchedulerRegistry } from '@nestjs/schedule';
import { RedmineDataLoader } from '../redmine-data-loader/redmine-data-loader';
import { randomUUID } from 'crypto';
import { RedmineTypes } from '../models/redmine-types';
import { CronJob } from 'cron';
import { ConfigService } from '@nestjs/config';

export type Task = {
  schedule: string;
  rootIssues: number[];
};

export type RootIssueListenerParams = {
  tasks: Task[];
};

@Injectable()
export class RootIssueListenerFactory {
  private rootIssueListener: RootIssueListener;

  constructor(
    private configService: ConfigService,
    private schedulerRegistry: SchedulerRegistry,
    private redmineDataLoader: RedmineDataLoader,
  ) {}

  getRootIssueListener(): RootIssueListener {
    if (!this.rootIssueListener) {
      const params = this.configService.get('rootIssueListener');
      this.rootIssueListener = new RootIssueListener(
        params,
        this.schedulerRegistry,
        'default_root_issue_listener',
        this.redmineDataLoader,
      );
    }
    return this.rootIssueListener;
  }
}

@Injectable()
export class RootIssueListener implements EventsListener {
  private logger = new Logger(RootIssueListener.name);

  issues: BehaviorSubject<number[]> = new BehaviorSubject([]);

  constructor(
    private params: RootIssueListenerParams,
    private schedulerRegistry: SchedulerRegistry,
    private jobPrefix: string,
    private redmineDataLoader: RedmineDataLoader,
  ) {
    this.logger.log(
      `Root issue listener created with params - ${JSON.stringify(params)}`,
    );
  }

  start(): void {
    const tasks = this?.params?.tasks || [];
    this.logger.debug(`Scheduling ${tasks.length} tasks`);
    for (let i = 0; i < tasks.length; i++) {
      const task = tasks[i];
      const cronJobName = this.createCronJobName();
      const cronJob = new CronJob(
        task.schedule,
        this.createLoader(task, cronJobName),
      );
      this.schedulerRegistry.addCronJob(cronJobName, cronJob);
      cronJob.start();
    }
  }

  stop(): void {
    const jobs = this.schedulerRegistry.getCronJobs();
    jobs.forEach((job, jobName) => {
      if (this.isListenerCronJon(jobName)) {
        job.stop();
        this.schedulerRegistry.deleteCronJob(jobName);
      }
    });
  }

  private createCronJobName(): string {
    return `${this.jobPrefix}_${randomUUID()}`;
  }

  private isListenerCronJon(name: string): boolean {
    return name.startsWith(`${this.jobPrefix}_`);
  }

  private createLoader(task: Task, cronJobName: string): () => Promise<void> {
    return async () => {
      this.logger.log(
        `Execute task ${cronJobName} ` +
          `by schedule ${task.schedule} ` +
          `with ${task.rootIssues.length} root issues`,
      );
      const issuesStore = await this.getRootIssuesFromRedmine(task);
      this.logger.debug(
        `Loaded root issues ` +
          `${Object.keys(issuesStore).length} - ` +
          `${JSON.stringify(Object.keys(issuesStore).map((i) => Number(i)))}`,
      );
      const ids = this.getAllRootIssueIds(Object.values(issuesStore), []);
      this.logger.debug(
        `Issues for update ${ids.length} - ${JSON.stringify(ids)}`,
      );
      if (ids && ids.length > 0) {
        this.issues.next(ids);
      }
    };
  }

  private async getRootIssuesFromRedmine(
    task: Task,
  ): Promise<Record<number, RedmineTypes.ExtendedIssue>> {
    const res = {};
    const SKIP_ENHANCERS = true;
    for (let i = 0; i < task.rootIssues.length; i++) {
      const issueId = task.rootIssues[i];
      if (typeof issueId !== 'number' || issueId <= 0) continue;
      const issue = await this.redmineDataLoader.loadIssue(
        issueId,
        SKIP_ENHANCERS,
      );
      if (issue) {
        res[issueId] = issue;
      }
    }
    return res;
  }

  private getAllRootIssueIds(
    issues: RedmineTypes.ExtendedIssue[] | RedmineTypes.Children,
    res: number[],
  ): number[] {
    for (let i = 0; i < issues.length; i++) {
      const issue = issues[i];
      if (issue.children && issue.children.length > 0) {
        res.push(issue.id);
        this.getAllRootIssueIds(issue.children, res);
      }
    }
    return res;
  }
}
