import { Application, Context } from '@midwayjs/koa';
import { Repository } from 'typeorm';
import * as toqm from 'typeorm';
import { get, isNull, isObject, isUndefined, pick } from 'lodash';
import { App, Inject } from '@midwayjs/core';
import { Opera } from './dbOpera';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { OperaService } from '../service/log/opera.service';
import { Tags } from '../entity/system/tags.entity';

export interface QueryOpera {
  column: string;
  value: any;
  /**采用数组的形式,依次嵌套操作 */
  opera: Opera[];
}
/**
 * create,udpate,delete: 创建,修改,删除都会记录日志,所以需要用BaseService的直接操作方法,要不不会产生日志
 */
export abstract class BaseService<T> {
  @App()
  app: Application;

  @Inject()
  ctx: Context;

  @Inject()
  operaLogsService: OperaService;
  abstract model: Repository<T>;

  @InjectEntityModel(Tags)
  tModel: Repository<Tags>;

  /**
   * 列表查询
   * @param query 查询条件(经过queryBuilder转换后的)
   * @param param 有关skip和limit,order_by(数组,多字段排列)
   */
  async query(query: QueryOpera[], { skip = 0, limit = 0, order = {} } = {}) {
    let findOpera: any = {};
    // 组织查询顺序
    let orderObject: any = {};
    // 如果有自定义顺序,则按照自定义顺序来, 没有自定义顺序,默认按创建时间的desc查询
    if (Object.keys(order).length > 0) {
      for (const column in order) orderObject[column] = order[column];
    } else orderObject = { created_time: 'DESC' };
    if (query.length > 0) findOpera = this.buildFindWhere(query);
    let builder;
    if ('tags' in findOpera) {
      const column = get(findOpera, 'tags');
      delete findOpera.tags;
      builder = await this.model.createQueryBuilder().setFindOptions({ where: findOpera, skip, take: limit, order: orderObject }).where('JSONB_EXISTS(tags, :column)', { column });
    } else {
      builder = await this.model.createQueryBuilder().setFindOptions({ where: findOpera, skip, take: limit, order: orderObject });
    }
    const data = await builder.getMany();
    const total = await builder.getCount();
    return { data, total };
  }

  /**根据builder设置转换成options */
  buildFindWhere(query) {
    const result = {};
    for (const qo of query) {
      let val = qo.value;
      for (const opera of qo.opera) {
        const methods = pick(toqm, ['IsNull', 'In', 'MoreThanOrEqual', 'MoreThan', 'LessThanOrEqual', 'LessThan', 'Equal', 'Not', 'Like', 'ILike', 'Between']);
        // const m = get(methods, Opera[opera]);
        const m = methods[Opera[opera]];
        // if 和 else if 都是需要特殊处理值的情况
        // Between这个一般都是最先处理,要放到数组最前面,否则会影响value的值
        if (Opera[opera] === Opera.Between) val = m(get(val, 0), get(val, 1));
        else if (Opera[opera] === Opera.Like || Opera[opera] === Opera.ILike) val = m(`%${val}%`);
        else if (Opera[opera] === Opera.LikeLeft || Opera[opera] === Opera.ILikeLeft) val = m(`%${val}`);
        else if (Opera[opera] === Opera.LikeRight || Opera[opera] === Opera.ILikeRight) val = m(`${val}%`);
        else if (Opera[opera] === Opera.IsNull) val = m![Opera[opera]]();
        else val = m(val);
      }
      result[qo.column] = val;
    }
    return result;
  }

  /**
   * 默认查询条件object
   * integer: 数字 默认 全等
   * character varying: 字符串 默认 模糊
   * timestamp without time zone: 时间 默认不处理,手写处理方式
   * @param {object} query 查询条件object
   * @return 输出为查询数组
   * [
   *  {
   *    column:字段,
   *    opera:查询方式
   *    value:具体值
   *  }
   * ]
   *  e.g.:
   * [
   *  {
   *    column:'nick_name',
   *    opera:eq,
   *    value:'admin'
   *  }
   * ]
   * 形成查询数组,再由query函数处理具体查询
   */
  queryBuilder(query: any = {}) {
    /**列设置,所有字段都需要设置type */
    const columns = this.model.metadata.columns;
    /**将array的列设置 转换为object,以便query使用*/
    const columnsObject = {};
    // 整理成object
    for (const c of columns) columnsObject[c.propertyName] = c.type.toString();
    /**返回的查询操作数组 */
    const queryArray: QueryOpera[] = [];
    // 根据查询条件(query),对字段进行处理
    for (const column in query) {
      const dataType = columnsObject[column];
      // 没找到字段,那就直接跳过,按理说不应该找不到.但是真的找不到,也不能添加进查询条件里,会出错
      if (!dataType) continue;
      if (dataType === 'integer') {
        const qo: QueryOpera = { column, value: query[column], opera: [Opera.Equal] };
        queryArray.push(qo);
      } else if (dataType === 'character varying') {
        const qo: QueryOpera = { column, value: query[column], opera: [Opera.Like] };
        queryArray.push(qo);
      } else if (dataType === 'timestamp without time zone') {
        //默认不处理
      } else if (dataType === 'jsonb') {
        if (column === 'tags') {
          const qo: QueryOpera = { column, value: query[column], opera: [] };
          queryArray.push(qo);
        }
      }
    }
    return queryArray;
  }

  /**单查询，不止用id还可以根据别的条件 */
  async fetch(query: QueryOpera[]) {
    const whereOptions = this.buildFindWhere(query);
    const builder = this.model.createQueryBuilder().setFindOptions({ where: whereOptions });
    const result = await builder.getOne();
    return result;
  }

  /**创建 */
  async create(data: object) {
    // 设置 创建数据的人
    if (this.ctx.user) Object.assign(data, { data_owner: this.ctx.user.id });
    const result = await this.model.insert(data);
    const mn = this.model.metadata.tableName;
    // 处理标签
    const tagsList: Array<any> = get(data, 'tags');
    if (tagsList && Array.isArray(tagsList) && tagsList.length > 0) await this.tagsAdd(tagsList);
    // 添加,没有原数据,只有新数据
    await this.operaLogsService.toMakeLogs(null, { [mn]: [data] });
    return result;
  }

  /**修改，单/多修改,多修改是统一修改为 */
  async update(query: object, data: object) {
    let where = {};
    if (query && isObject(query)) where = query;
    // 处理数据, 只将是本表的字段拿出来保存
    const columns = this.model.metadata.columns;
    /**将array的列设置 转换为object,以便query使用*/
    const columnsObject = {};
    // 整理成object
    for (const c of columns) columnsObject[c.propertyName] = c.type.toString();
    const updateData = {};
    const notDealColumn = ['id', 'created_time', 'update_time', 'data_owner', '__v'];
    for (const column in columnsObject) {
      if (notDealColumn.includes(column)) continue;
      const val = data[column];
      if (isNull(val) || isUndefined(val)) continue;
      updateData[column] = val;
    }
    // 找到原数据
    const origin_data = await this.model.find({ where });
    const result = await this.model.update(where, updateData);
    // 修改成新的数据,result只是结果,需要重新查一遍
    const new_data = await this.model.find({ where });
    const mn = this.model.metadata.tableName;
    // 处理标签
    const tagsList: Array<any> = get(data, 'tags');
    if (tagsList && Array.isArray(tagsList) && tagsList.length > 0) await this.tagsAdd(tagsList);
    // 日志
    await this.operaLogsService.toMakeLogs({ [mn]: origin_data }, { [mn]: new_data });
    return result;
  }

  /**删除，单删多删都行，假删除，把删除的数据移到指定删除表中 */
  async delete(where: object) {
    // 删除前,先查出来数据
    const origin_data = await this.model.find({ where });
    const result = await this.model.delete(where);
    const mn = this.model.metadata.tableName;
    // 日志,只有原数据没有新数据
    await this.operaLogsService.toMakeLogs({ [mn]: origin_data });
    return result;
  }

  /**处理标签，标签表内有不处理,没有标签新增 */
  async tagsAdd(tagsList: Array<any>) {
    for (const val of tagsList) {
      const where = { title: val };
      const tags = await this.tModel.findOne({ where });
      if (!tags) await this.tModel.save(where);
    }
  }
}
