import axios from 'axios';
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { RedmineTypes } from '../models/redmine-types';
import { EnhancerService } from '../issue-enhancers/enhancer.service';
import { parse as csvParse } from 'csv/sync';

@Injectable()
export class RedmineDataLoader {
  urlPrefix: string;
  redmineUrl: string;
  redmineToken: string;

  private logger = new Logger(RedmineDataLoader.name);

  constructor(
    private configService: ConfigService,
    private enhancerService: EnhancerService,
  ) {
    this.urlPrefix = this.configService.get<string>('redmineUrlPrefix');
    this.redmineUrl = this.configService.get<string>('redmineUrlPublic');
    this.redmineToken = this.configService.get<string>('redmineToken');
  }

  async loadIssues(issues: number[]): Promise<(RedmineTypes.Issue | null)[]> {
    const promises = issues.map((issue) => this.loadIssue(issue));
    return Promise.all(promises);
  }

  async loadIssue(
    issueNumber: number,
    skipEnhancers = false,
  ): Promise<RedmineTypes.Issue | null> {
    if (issueNumber <= 0) {
      return null;
    }
    const url = this.getIssueUrl(issueNumber);
    let resp;
    try {
      resp = await axios.get(url);
    } catch (ex) {
      const errorMsg = ex.message || 'Unknown error';
      this.logger.error(`${errorMsg} for url = ${url}`);
      throw ex;
    }
    if (!resp || !resp.data || !resp.data.issue) {
      this.logger.error(
        `Failed to load issue from redmine, issueNumber = ${issueNumber}`,
      );
      return null;
    }
    this.logger.debug(
      `Loaded issue, issueNumber = ${issueNumber}, subject = ${resp.data.issue.subject}`,
    );
    if (skipEnhancers) return resp.data.issue;
    let enhancedIssue;
    try {
      enhancedIssue = await this.enhancerService.enhanceIssue(resp.data.issue);
    } catch (ex) {
      const errorMsg = ex.message || 'Unknown error';
      this.logger.error(`${errorMsg} at enhance issue #${issueNumber}`);
      throw ex;
    }
    return enhancedIssue;
  }

  async loadUsers(users: number[]): Promise<(RedmineTypes.User | null)[]> {
    const promises = users.map((user) => this.loadUser(user));
    return Promise.all(promises);
  }

  async loadUser(userNumber: number): Promise<RedmineTypes.User | null> {
    if (typeof userNumber !== 'number' || userNumber <= 0) {
      this.logger.warn(`Invalid userNumber = ${userNumber}`);
      return null;
    }
    const url = this.getUserUrl(userNumber);
    let resp;
    try {
      resp = await axios.get(url);
    } catch (ex) {
      const errorMsg = ex.message || 'Unknown error';
      this.logger.error(`${errorMsg} at load user by url ${url}`);
      return null;
    }
    if (!resp || !resp.data?.user) {
      this.logger.error(
        `Failed to load user from redmine, userNumber = ${userNumber}`,
      );
      return null;
    }
    const user: RedmineTypes.User = resp.data.user;
    this.logger.debug(
      `Loaded user, userNumber = ${userNumber}, login = ${user.login}, firstname = ${user.firstname}, lastname = ${user.lastname}`,
    );
    return RedmineTypes.CreateUser(user);
  }

  private getIssueUrl(issueNumber: number): string {
    if (typeof this.urlPrefix !== 'string' || this.urlPrefix.length === 0) {
      throw 'REDMINE_URL_PREFIX is undefined';
    }
    return `${this.urlPrefix}/issues/${issueNumber}.json?include=children,journals,relations`;
  }

  private getUserUrl(userNumber: number): string {
    return `${this.urlPrefix}/users/${userNumber}.json`;
  }

  async loadCsv(
    urlQuery: string,
    csvParserParams?: any,
  ): Promise<Record<string, any>[]> {
    if (!csvParserParams) {
      csvParserParams = {
        delimiter: ';',
        quote: '"',
        columns: true,
        skip_empty_lines: true,
      };
    }
    let resp;
    try {
      resp = await fetch(urlQuery, {
        headers: {
          'X-Redmine-API-Key': this.redmineToken,
        },
      });
    } catch (err) {
      this.logger.error(
        `Failed to fetch data from Redmine by url ${urlQuery}: ${err}`,
      );
      return null;
    }
    const rawData = await resp.text();
    let res;
    try {
      res = csvParse(rawData, csvParserParams);
    } catch (ex) {
      this.logger.error(
        `Error at loading csv from redmine, query - ${urlQuery}, ex - ${ex}`,
      );
      return null;
    }
    return res;
  }
}
