import { Logger } from '@nestjs/common';
import { UserProfile, db, RowModelRequest } from '@nice/common';
import { LogicalCondition, OperatorType, SQLBuilder } from './sql-builder';
export interface GetRowOptions {
  id?: string;
  ids?: string[];
  extraCondition?: LogicalCondition;
  staff?: UserProfile;
}
export abstract class RowModelService {
  private keywords: Set<string> = new Set([
    'SELECT',
    'FROM',
    'WHERE',
    'ORDER',
    'BY',
    'GROUP',
    'JOIN',
    'AND',
    'OR',
    // 添加更多需要引号的关键词
  ]);
  protected logger = new Logger(this.tableName);
  protected constructor(protected tableName: string) { }
  protected async getRowDto(row: any, staff?: UserProfile): Promise<any> {
    return row;
  }
  protected async getRowsSqlWrapper(
    sql: string,
    request?: RowModelRequest,
    staff?: UserProfile,
  ) {
    if (request) return SQLBuilder.join([sql, this.getLimitSql(request)]);
    return sql;
  }
  protected getLimitSql(request: RowModelRequest) {
    return SQLBuilder.limit(
      request.endRow - request.startRow,
      request.startRow,
    );
  }
  abstract createJoinSql(request?: RowModelRequest): string[];
  async getRows(request: RowModelRequest, staff?: UserProfile) {
    try {
      let SQL = SQLBuilder.join([
        SQLBuilder.select(this.getRowSelectCols(request)),
        SQLBuilder.from(this.tableName),
        SQLBuilder.join(this.createJoinSql(request)),
        SQLBuilder.where(this.createGetRowsFilters(request, staff)),
        SQLBuilder.groupBy(this.getGroupByColumns(request)),
        SQLBuilder.orderBy(this.getOrderByColumns(request)),
      ]);
      SQL = await this.getRowsSqlWrapper(SQL, request, staff);

      // this.logger.debug('getrows', SQL);

      const results: any[] = (await db?.$queryRawUnsafe(SQL)) || [];

      const rowDataDto = await Promise.all(
        results.map((row) => this.getRowDto(row, staff)),
      );
      return {
        rowCount: this.getRowCount(request, rowDataDto) || 0,
        rowData: rowDataDto,
      };
    } catch (error: any) {
      this.logger.error('Error executing getRows:', error);
    }
  }
  getRowCount(request: RowModelRequest, results: any[]) {
    if (results === null || results === undefined || results.length === 0) {
      return null;
    }
    const currentLastRow = request.startRow + results.length;
    return currentLastRow <= request.endRow ? currentLastRow : -1;
  }

  async getRowById(options: GetRowOptions): Promise<any> {
    const {
      id,
      extraCondition = {
        field: `${this.tableName}.deleted_at`,
        op: 'blank',
        type: 'date',
      },
      staff,
    } = options;
    return this.getSingleRow(
      { AND: [this.createGetByIdFilter(id!), extraCondition] },
      staff,
    );
  }

  async getRowByIds(options: GetRowOptions): Promise<any[]> {
    const {
      ids,
      extraCondition = {
        field: `${this.tableName}.deleted_at`,
        op: 'blank',
        type: 'date',
      },
      staff,
    } = options;
    return this.getMultipleRows(
      { AND: [this.createGetByIdsFilter(ids!), extraCondition] },
      staff,
    );
  }

  protected createGetRowsFilters(
    request: RowModelRequest,
    staff?: UserProfile,
  ): LogicalCondition {
    let groupConditions: LogicalCondition[] = [];
    if (this.isDoingTreeGroup(request)) {
      groupConditions = [
        {
          field: 'parent_id',
          op: 'equals' as OperatorType,
          value: request.groupKeys[request.groupKeys.length - 1],
        },
      ];
    } else {
      groupConditions = request?.groupKeys?.map((key, index) => ({
        field: request.rowGroupCols[index].field,
        op: 'equals' as OperatorType,
        value: key,
      }));
    }

    const condition: LogicalCondition = {
      AND: [
        ...groupConditions,
        ...this.buildFilterConditions(request.filterModel),
      ],
    };

    return condition;
  }
  private buildFilterConditions(filterModel: any): LogicalCondition[] {
    return filterModel
      ? Object.entries(filterModel)?.map(([key, item]) =>
        SQLBuilder.createFilterSql(
          key === 'ag-Grid-AutoColumn' ? 'name' : key,
          item,
        ),
      )
      : [];
  }

  getRowSelectCols(request: RowModelRequest): string[] {
    return this.isDoingGroup(request)
      ? this.createGroupingRowSelect(request)
      : this.createUnGroupingRowSelect(request);
  }
  protected createUnGroupingRowSelect(request?: RowModelRequest): string[] {
    return ['*'];
  }
  protected createAggSqlForWrapper(request: RowModelRequest) {
    const { rowGroupCols, valueCols, groupKeys } = request;
    return valueCols.map(
      (valueCol) =>
        `${valueCol.aggFunc}(${valueCol.field.replace('.', '_')}) AS ${valueCol.field.split('.').join('_')}`,
    );
  }
  protected createGroupingRowSelect(
    request: RowModelRequest,
    wrapperSql: boolean = false,
  ): string[] {
    const { rowGroupCols, valueCols, groupKeys } = request;
    const colsToSelect: string[] = [];

    const rowGroupCol = rowGroupCols[groupKeys!.length];
    if (rowGroupCol) {
      colsToSelect.push(
        `${rowGroupCol.field} AS ${rowGroupCol.field.replace('.', '_')}`,
      );
    }
    colsToSelect.push(
      ...valueCols.map(
        (valueCol) =>
          `${wrapperSql ? '' : valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace('.', '_')}`,
      ),
    );

    return colsToSelect;
  }

  getGroupByColumns(request: RowModelRequest): string[] {
    return this.isDoingGroup(request)
      ? [request.rowGroupCols[request.groupKeys!.length]?.field]
      : [];
  }

  getOrderByColumns(request: RowModelRequest): string[] {
    const { sortModel, rowGroupCols, groupKeys } = request;
    const grouping = this.isDoingGroup(request);
    const sortParts: string[] = [];

    if (sortModel) {
      const groupColIds = rowGroupCols
        .map((groupCol) => groupCol.id)
        .slice(0, groupKeys.length + 1);
      sortModel.forEach((item) => {
        if (
          !grouping ||
          (groupColIds.indexOf(item.colId) >= 0 &&
            rowGroupCols[groupKeys.length].field === item.colId)
        ) {
          const colId = this.keywords.has(item.colId.toUpperCase())
            ? `"${item.colId}"`
            : item.colId;
          sortParts.push(`${colId} ${item.sort}`);
        }
      });
    }

    return sortParts;
  }
  isDoingGroup(requset: RowModelRequest): boolean {
    return requset.rowGroupCols.length > requset.groupKeys.length;
  }
  isDoingTreeGroup(requset: RowModelRequest): boolean {
    return requset.rowGroupCols.length === 0 && requset.groupKeys.length > 0;
  }
  private async getSingleRow(
    condition: LogicalCondition,
    staff?: UserProfile,
  ): Promise<any> {
    const results = await this.getRowsWithFilters(condition, staff);
    return results[0];
  }
  private async getMultipleRows(
    condition: LogicalCondition,
    staff?: UserProfile,
  ): Promise<any[]> {
    return this.getRowsWithFilters(condition, staff);
  }

  private async getRowsWithFilters(
    condition: LogicalCondition,
    staff?: UserProfile,
  ): Promise<any[]> {
    try {
      const SQL = SQLBuilder.join([
        SQLBuilder.select(this.createUnGroupingRowSelect()),
        SQLBuilder.from(this.tableName),
        SQLBuilder.join(this.createJoinSql()),
        SQLBuilder.where(condition),
      ]);

      // this.logger.debug(SQL)
      const results: any[] = await db.$queryRawUnsafe(SQL);

      const rowDataDto = await Promise.all(
        results.map((item) => this.getRowDto(item, staff)),
      );

      // rowDataDto = getUniqueItems(rowDataDto, "id")
      return rowDataDto;
    } catch (error) {
      this.logger.error('Error executing query:', error);
      throw error;
    }
  }

  async getAggValues(request: RowModelRequest) {
    try {
      const SQL = SQLBuilder.join([
        SQLBuilder.select(this.buildAggSelect(request.valueCols)),
        SQLBuilder.from(this.tableName),
        SQLBuilder.join(this.createJoinSql(request)),
        SQLBuilder.where(this.createGetRowsFilters(request)),
        SQLBuilder.groupBy(this.buildAggGroupBy()),
      ]);
      const result: any[] = await db.$queryRawUnsafe(SQL);
      return result[0];
    } catch (error) {
      this.logger.error('Error executing query:', error);
      throw error;
    }
  }
  protected buildAggGroupBy(): string[] {
    return [];
  }
  protected buildAggSelect(valueCols: any[]): string[] {
    return valueCols.map(
      (valueCol) =>
        `${valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace('.', '_')}`,
    );
  }

  private createGetByIdFilter(id: string): LogicalCondition {
    return {
      field: `${this.tableName}.id`,
      value: id,
      op: 'equals',
    };
  }
  private createGetByIdsFilter(ids: string[]): LogicalCondition {
    return {
      field: `${this.tableName}.id`,
      value: ids,
      op: 'in',
    };
  }
}
