import { Condition, Expression, Select } from 'dbgate-sqltree';
import { PerspectiveDataLoadProps } from './PerspectiveDataProvider';
import debug from 'debug';
import _zipObject from 'lodash/zipObject';
import _mapValues from 'lodash/mapValues';
import _isArray from 'lodash/isArray';
import { safeJsonParse } from 'dbgate-tools';
import { CollectionAggregateDefinition } from 'dbgate-types';

function normalizeLoadedRow(row) {
  return _mapValues(row, v => safeJsonParse(v) || v);
}

function normalizeResult(result) {
  if (_isArray(result)) {
    return result.map(normalizeLoadedRow);
  }
  if (result.errorMessage) {
    return result;
  }
  return {
    ...result,
    errorMessage: 'Unspecified error',
  };
}

const dbg = debug('dbgate:PerspectiveDataLoader');

export class PerspectiveDataLoader {
  constructor(public apiCall) {}

  buildSqlCondition(props: PerspectiveDataLoadProps): Condition {
    const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, sqlCondition } = props;

    const conditions = [];

    if (sqlCondition) {
      conditions.push(sqlCondition);
    }

    if (bindingColumns?.length == 1) {
      conditions.push({
        conditionType: 'in',
        expr: {
          exprType: 'column',
          columnName: bindingColumns[0],
          source: {
            name: { schemaName, pureName },
          },
        },
        values: bindingValues.map(x => x[0]),
      });
    }

    return conditions.length > 0
      ? {
          conditionType: 'and',
          conditions,
        }
      : null;
  }

  async loadGroupingSqlDb(props: PerspectiveDataLoadProps) {
    const { schemaName, pureName, bindingColumns } = props;

    const bindingColumnExpressions = bindingColumns.map(
      columnName =>
        ({
          exprType: 'column',
          columnName,
          source: {
            name: { schemaName, pureName },
          },
        } as Expression)
    );

    const select: Select = {
      commandType: 'select',
      from: {
        name: { schemaName, pureName },
      },
      columns: [
        {
          exprType: 'call',
          func: 'COUNT',
          args: [
            {
              exprType: 'raw',
              sql: '*',
            },
          ],
          alias: '_perspective_group_size_',
        },
        ...bindingColumnExpressions,
      ],
      where: this.buildSqlCondition(props),
    };

    select.groupBy = bindingColumnExpressions;

    if (dbg?.enabled) {
      dbg(`LOAD COUNTS, table=${props.pureName}, columns=${bindingColumns?.join(',')}`);
    }

    const response = await this.apiCall('database-connections/sql-select', {
      conid: props.databaseConfig.conid,
      database: props.databaseConfig.database,
      select,
      auditLogSessionGroup: 'perspective',
    });

    if (response.errorMessage) return response;
    return response.rows.map(row => ({
      ...row,
      _perspective_group_size_: parseInt(row._perspective_group_size_),
    }));
  }

  async loadGroupingDocDb(props: PerspectiveDataLoadProps) {
    const { schemaName, pureName, bindingColumns } = props;

    const aggregate: CollectionAggregateDefinition = {
      condition: this.buildSqlCondition(props),
      groupByColumns: bindingColumns,
      aggregateColumns: [
        {
          alias: 'pergrpsize',
          aggregateFunction: 'count',
        },
      ],
    };
    // const aggregate = [
    //   { $match: this.buildMongoCondition(props) },
    //   {
    //     $group: {
    //       _id: _zipObject(
    //         bindingColumns,
    //         bindingColumns.map(col => '$' + col)
    //       ),
    //       count: { $sum: 1 },
    //     },
    //   },
    // ];

    if (dbg?.enabled) {
      dbg(`LOAD COUNTS, table=${props.pureName}, columns=${bindingColumns?.join(',')}`);
    }

    const response = await this.apiCall('database-connections/collection-data', {
      conid: props.databaseConfig.conid,
      database: props.databaseConfig.database,
      options: {
        pureName,
        aggregate,
      },
      auditLogSessionGroup: 'perspective',
    });

    if (response.errorMessage) return response;
    return response.rows.map(row => ({
      ...row,
      _perspective_group_size_: parseInt(row.pergrpsize),
    }));
  }

  async loadGrouping(props: PerspectiveDataLoadProps) {
    const { engineType } = props;
    switch (engineType) {
      case 'sqldb':
        return this.loadGroupingSqlDb(props);
      case 'docdb':
        return this.loadGroupingDocDb(props);
    }
  }

  async loadDataSqlDb(props: PerspectiveDataLoadProps) {
    const {
      schemaName,
      pureName,
      bindingColumns,
      bindingValues,
      dataColumns,
      orderBy,
      sqlCondition: condition,
      engineType,
    } = props;

    if (dataColumns?.length == 0) {
      return [];
    }

    const select: Select = {
      commandType: 'select',
      from: {
        name: { schemaName, pureName },
      },
      columns: dataColumns?.map(columnName => ({
        exprType: 'column',
        columnName,
        source: {
          name: { schemaName, pureName },
        },
      })),
      selectAll: !dataColumns,
      orderBy:
        orderBy?.length > 0
          ? orderBy?.map(({ columnName, order }) => ({
              exprType: 'column',
              columnName,
              direction: order,
              source: {
                name: { schemaName, pureName },
              },
            }))
          : null,
      range: props.range,
      where: this.buildSqlCondition(props),
    };

    if (dbg?.enabled) {
      dbg(
        `LOAD DATA, table=${props.pureName}, columns=${props.dataColumns?.join(',')}, range=${props.range?.offset},${
          props.range?.limit
        }`
      );
    }

    const response = await this.apiCall('database-connections/sql-select', {
      conid: props.databaseConfig.conid,
      database: props.databaseConfig.database,
      select,
      auditLogSessionGroup: 'perspective',
    });

    if (response.errorMessage) return response;
    return response.rows;
  }

  getDocDbLoadOptions(props: PerspectiveDataLoadProps, useSort: boolean) {
    const { pureName } = props;
    const res: any = {
      pureName,
      condition: this.buildSqlCondition(props),
      sort:
        useSort && props.orderBy?.length > 0
          ? props.orderBy.map(x => ({
              ...x,
              direction: x.order,
            }))
          : undefined,
      skip: props.range?.offset,
      limit: props.range?.limit,
    };
    // if (useSort && props.orderBy?.length > 0) {
    //   res.sort = _zipObject(
    //     props.orderBy.map(col => col.columnName),
    //     props.orderBy.map(col => (col.order == 'DESC' ? -1 : 1))
    //   );
    // }

    return res;
  }

  async loadDataDocDb(props: PerspectiveDataLoadProps) {
    const {
      schemaName,
      pureName,
      bindingColumns,
      bindingValues,
      dataColumns,
      orderBy,
      sqlCondition: condition,
      engineType,
    } = props;

    if (dbg?.enabled) {
      dbg(
        `LOAD DATA, collection=${props.pureName}, columns=${props.dataColumns?.join(',')}, range=${
          props.range?.offset
        },${props.range?.limit}`
      );
    }

    const options = this.getDocDbLoadOptions(props, true);

    const response = await this.apiCall('database-connections/collection-data', {
      conid: props.databaseConfig.conid,
      database: props.databaseConfig.database,
      options,
      auditLogSessionGroup: 'perspective',
    });

    if (response.errorMessage) return response;
    return response.rows;
  }

  async loadData(props: PerspectiveDataLoadProps) {
    const { engineType } = props;
    switch (engineType) {
      case 'sqldb':
        return normalizeResult(await this.loadDataSqlDb(props));
      case 'docdb':
        return normalizeResult(await this.loadDataDocDb(props));
    }
  }

  async loadRowCountSqlDb(props: PerspectiveDataLoadProps) {
    const {
      schemaName,
      pureName,
      bindingColumns,
      bindingValues,
      dataColumns,
      orderBy,
      sqlCondition: condition,
    } = props;

    const select: Select = {
      commandType: 'select',
      from: {
        name: { schemaName, pureName },
      },
      columns: [
        {
          exprType: 'raw',
          sql: 'COUNT(*)',
          alias: 'count',
        },
      ],
      where: this.buildSqlCondition(props),
    };

    const response = await this.apiCall('database-connections/sql-select', {
      conid: props.databaseConfig.conid,
      database: props.databaseConfig.database,
      select,
      auditLogSessionGroup: 'perspective',
    });

    if (response.errorMessage) return response;
    return response.rows[0];
  }

  async loadRowCountDocDb(props: PerspectiveDataLoadProps) {
    const {
      schemaName,
      pureName,
      bindingColumns,
      bindingValues,
      dataColumns,
      orderBy,
      sqlCondition: condition,
    } = props;

    const options = {
      ...this.getDocDbLoadOptions(props, false),
      countDocuments: true,
    };

    const response = await this.apiCall('database-connections/collection-data', {
      conid: props.databaseConfig.conid,
      database: props.databaseConfig.database,
      options,
      auditLogSessionGroup: 'perspective',
    });

    return response;
  }

  async loadRowCount(props: PerspectiveDataLoadProps) {
    const { engineType } = props;
    switch (engineType) {
      case 'sqldb':
        return this.loadRowCountSqlDb(props);
      case 'docdb':
        return this.loadRowCountDocDb(props);
    }
  }
}
