import { Injectable, Logger, Scope } from '@nestjs/common';
import { BaseReportService } from '../shared/services/base-report.service';
import { GetReportConfigDto } from '../shared/dto/report-config.get.dto';
import { GetReportDataDto } from '../shared/dto/report-data.get.dto';
import { ReportConfigs } from '../shared/configs';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { DataSource, Repository } from 'typeorm';
import { ReportConfigEntity } from '../shared/entities/report-config.entity';
import { ReportQueryBuilder } from '../shared/helpers';
import { DATA_FORMAT, REPORT_BOOKMARK_TYPE } from '../shared/constant';
import { roundingCurrency } from '../shared/helpers';
import { ReportBookmarkModel } from '../shared/models/report-bookmark.model';

@Injectable({ scope: Scope.REQUEST })
export class ReportService extends BaseReportService {
  private readonly logger = new Logger(ReportService.name);

  constructor(
    @InjectDataSource(CONNECTION_NAME.DEFAULT)
    private dataSource: DataSource,

    @InjectRepository(ReportBookmarkModel, CONNECTION_NAME.DEFAULT)
    private bookmarkRepo: Repository<ReportBookmarkModel>,
  ) {
    super();
  }

  async getReportConfig(query: GetReportConfigDto) {
    const { unique_names = [], group_names = [] } = query;

    let configs = ReportConfigs;
    if (group_names.length > 0) {
      configs = configs.filter((item) => group_names.includes(item.group_name));
    }

    if (unique_names.length > 0) {
      configs = configs.filter((item) =>
        unique_names.includes(item.unique_name),
      );
    }
    const groups = Array.from(new Set(configs?.map((item) => item.group_name)));
    const names = Array.from(new Set(configs?.map((item) => item.unique_name)));

    const modelName = ReportBookmarkModel.name;
    const creator_id = this.getUser().id;

    const bookmarkConfigs = await this.bookmarkRepo
      .createQueryBuilder(modelName)
      .where((query) => {
        if (names.length > 0) {
          query.andWhere(`group_name IN (:...groups)`, { groups });
        }
        if (groups.length > 0) {
          query.andWhere(`unique_name IN (:...names)`, { names });
        }

        query.andWhere(`creator_id = :creator_id`, { creator_id });
        query.andWhere(`applied = :applied`, { applied: true });
      })
      .getMany();

    return configs.map((item) => {
      const active_filter = bookmarkConfigs.find(
        (conf) =>
          conf.group_name === item.group_name &&
          conf.unique_name === item.unique_name &&
          conf.type === REPORT_BOOKMARK_TYPE.FILTER_TABLE,
      );

      const active_table_config = bookmarkConfigs.find(
        (conf) =>
          conf.group_name === item.group_name &&
          conf.unique_name === item.unique_name &&
          conf.type === REPORT_BOOKMARK_TYPE.TABLE_CONFIG,
      );
      return {
        ...item,
        active_filter: active_filter ?? {},
        active_table_config: active_table_config ?? {},
      };
    });
  }

  getReportConfigByUniqueName(group_name, unique_name): ReportConfigEntity {
    return ReportConfigs.find(
      (item) =>
        item.unique_name === unique_name && item.group_name === group_name,
    );
  }

  async getReportData(body: GetReportDataDto) {
    try {
      const queryModel = body.query_model;
      const reportConfig = this.getReportConfigByUniqueName(
        body.group_name,
        body.unique_name,
      );
      const builder = new ReportQueryBuilder(reportConfig, queryModel);
      const SQL = builder.getSql();
      const queryResult = await this.dataSource.query(SQL);
      const realData = [];
      const configColumns = reportConfig.column_configs;

      for (const item of queryResult) {
        const realItem = {};
        for (const itemKey of Object.keys(item)) {
          if (itemKey === 'count_child_group') {
            const realValue = item[itemKey] ?? 0;
            Object.assign(realItem, { [`${itemKey}`]: Number(realValue) });
          } else {
            const confCol = configColumns.find((c) => c.column === itemKey);
            const isNumber = confCol.format === DATA_FORMAT.NUMBER;
            const isCurrency = confCol.format === DATA_FORMAT.CURRENCY;
            const isMinusCurrency =
              confCol.format === DATA_FORMAT.MINUS_CURRENCY;
            const isBoolean = confCol.format === DATA_FORMAT.BOOLEAN;
            const isPercentage = confCol.format === DATA_FORMAT.PERCENTAGE;
            const isSetInitNull = isNumber || isCurrency || isMinusCurrency;
            const isTextUpperCase =
              confCol.format === DATA_FORMAT.TEXT_UPPERCASE;
            const isTextLowerCase =
              confCol.format === DATA_FORMAT.TEXT_LOWERCASE;

            if (isSetInitNull) {
              const realValue = item[itemKey] ?? 0;
              if (isCurrency) {
                Object.assign(realItem, {
                  [`${itemKey}`]: roundingCurrency(realValue),
                });
              } else if (isMinusCurrency) {
                Object.assign(realItem, {
                  [`${itemKey}`]: roundingCurrency(realValue) * -1,
                });
              } else {
                Object.assign(realItem, { [`${itemKey}`]: realValue });
              }
            } else if (isPercentage) {
              const realValue = item[itemKey]
                ? `${item[itemKey]}%`
                : item[itemKey];
              Object.assign(realItem, { [`${itemKey}`]: realValue });
            } else if (isBoolean) {
              let realValue = '';
              if (item[itemKey] === true || item[itemKey] === 1)
                realValue = 'Yes';
              else if (item[itemKey] === false || item[itemKey] === 0)
                realValue = 'No';
              Object.assign(realItem, { [`${itemKey}`]: realValue });
            } else if (isTextUpperCase) {
              Object.assign(realItem, {
                [`${itemKey}`]: item[itemKey]?.toUpperCase(),
              });
            } else if (isTextLowerCase) {
              Object.assign(realItem, {
                [`${itemKey}`]: item[itemKey]?.toLowerCase(),
              });
            } else {
              Object.assign(realItem, { [`${itemKey}`]: item[itemKey] });
            }
          }
        }
        realData.push(realItem);
      }

      return realData;
    } catch (error) {
      this.logger.error(error);
      throw error;
    }
  }

  async getReportMeta(body: GetReportDataDto) {
    try {
      const queryModel = body.query_model;
      const reportConfig = this.getReportConfigByUniqueName(
        body.group_name,
        body.unique_name,
      );
      const builder = new ReportQueryBuilder(reportConfig, queryModel);
      const SQL_COUNT = builder.getSqlCount();
      const queryResult = await this.dataSource.query(SQL_COUNT);

      const totalRow = parseInt(queryResult[0].count);
      const startRow = queryModel.startRow;
      const endRow = queryModel.endRow;
      const pageSize = endRow - startRow;

      const meta = {
        total_row: totalRow,
        limit: pageSize,
        offset: startRow,
      };

      return meta;
    } catch (error) {
      this.logger.error(error);
      throw error;
    }
  }
}
