import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';
import { CreateDto } from 'src/common/dto/create.dto';
import { Train } from 'src/train/entities/train/train.entity';
import { PaginationDTO } from 'src/assessment-evaluation/dto/pagination.dto';
import { TrainSettings } from 'src/train/entities/train/train-settings.entity';
import { TrainOperationLog } from 'src/train/entities/train/train-operation-log.entity';
import { SubjectStep } from 'src/train/entities/subject/subject-step.entity';
import { Subject } from 'src/train/entities/subject/subject.entity';
import { Device } from 'src/train/entities/device/device.entity';
import { TrainScoreLog } from 'src/train/entities/train/train-score-log.entity';
import { User } from 'src/system/entities/user.entity';
import { DeviceIP } from 'src/train/entities/device/device-ip.entity';

function formatQueryRes(data: Array<any>, body: string, keySep = '__') {
  return data.map((item) => {
    const newItem = {};
    Object.keys(item).forEach((key) => {
      if (key.includes(body + keySep)) {
        newItem[key.substring((body + keySep).length)] = item[key];
      } else {
        const keyAndNKey = key.split('__');
        if (!newItem[keyAndNKey[0]]) {
          newItem[keyAndNKey[0]] = {};
        }
        newItem[keyAndNKey[0]][keyAndNKey[1]] = item[key];
      }
    });
    return newItem;
  });
}
@Injectable()
export class TrainService {
  constructor(
    @InjectRepository(Train)
    private TrainRep: Repository<Train>,
    @InjectRepository(TrainSettings)
    private TrainSettingsRep: Repository<TrainSettings>,
    @InjectRepository(TrainOperationLog)
    private TOLogsRep: Repository<TrainOperationLog>,
    @InjectRepository(SubjectStep)
    private SubjectStepRep: Repository<SubjectStep>,
    @InjectRepository(TrainScoreLog)
    private TrainScoreLogRep: Repository<TrainScoreLog>,
  ) {}

  async create(createClassifyDto: CreateTrainDto) {
    const selectSubjectSteps = await this.SubjectStepRep.find({
      where: {
        subject: createClassifyDto.subject,
      },
      order: { sort: 'ASC' },
    });
    // 校验
    if (!selectSubjectSteps.length) throw '科目中没有步骤';
    if (!createClassifyDto.setting) throw '没有人员操作信息';
    const lengths = createClassifyDto.setting.map((item) => item.length);
    if (Math.max(...lengths) != Math.min(...lengths))
      throw '各组中人员操作数量不同';
    const trainEx = await this.TrainRep.save(createClassifyDto);
    const settings = createClassifyDto.setting;
    const settingsList = [];
    // 处理所有人员操作信息
    for (let i = 0; i < settings.length; i++) {
      const element = settings[i];
      for (let j = 0; j < element.length; j++) {
        const item = element[j];
        settingsList.push({
          creator: createClassifyDto.creator,
          updater: createClassifyDto.updater,
          train: trainEx.id,
          type: createClassifyDto.type,
          ...item,
        });
      }
    }
    const settingsRes = await this.TrainSettingsRep.save(settingsList);
    // 获取科目所有步骤
    const logsList = [];
    for (let group = 1; group <= settings.length; group++) {
      for (let stepInd = 0; stepInd < selectSubjectSteps.length; stepInd++) {
        const stepItem = selectSubjectSteps[stepInd];
        const stepData: any = {
          creator: createClassifyDto.creator,
          updater: createClassifyDto.updater,
          train: trainEx.id,
          step: stepItem.id,
        };
        const thisStepDevice = stepItem.device;
        const itemsSetting = settingsRes.find((item) => {
          return item.device == thisStepDevice && item.group == group;
        });
        if (!itemsSetting) throw `没有找到该进行该步骤${stepItem}的操作设置`;
        stepData.setting = itemsSetting.id;
        logsList.push(stepData);
      }
    }
    await this.TOLogsRep.insert(logsList);

    return trainEx;
  }
  /**
   * 根据训练ID获取所有步骤信息
   * @param trainId
   */
  async findAllStepByTrainId(trainId: number) {
    const res = await this.TOLogsRep.query(
      `select
      -- 	tol.create_time as tol__create_time,
      -- 	tol.creator as tol__creator,
      -- 	tol.update_time as tol__update_time ,
      -- 	tol.updater as tol__updater,
      -- 	tol.del_flag as tol__del_flag,
      -- 	tol.remarks as tol__remarks,
      -- 	tol.version as tol__version,
        tol.id as tol__id,
        tol.train as tol__train,
        tol.step as tol__step,
        tol.status as tol__status,
        tol.duration as tol__duration,
        tol.pause as tol__pause,
        tol.setting as tol__setting,
        
      -- 	ss.create_time as stepInfo__create_time,
      -- 	ss.creator as stepInfo__creator,
      -- 	ss.update_time as stepInfo__update_time,
      -- 	ss.updater as stepInfo__updater,
      -- 	ss.del_flag as stepInfo__del_flag,
      -- 	ss.remarks as stepInfo__remarks,
      -- 	ss.version as stepInfo__version,
        ss.id as stepInfo__id,
        ss.subject as stepInfo__subject,
        ss.sort as stepInfo__sort,
        ss.delay as stepInfo__delay,
        ss.duration as stepInfo__duration,
        ss.comment as stepInfo__comment,
        ss.operation as stepInfo__operation,
        ss.device as stepInfo__device,
        ss.part as stepInfo__part,
        
      -- 	u.create_time as userInfo__create_time,
      -- 	u.creator as userInfo__creator,
      -- 	u.update_time as userInfo__update_time,
      -- 	u.updater as userInfo__updater,
      -- 	u.del_flag as userInfo__del_flag,
      -- 	u.remarks as userInfo__remarks,
      -- 	u.version as userInfo__version,
        u.id as userInfo__id,
        u.username as userInfo__username,
        u.password as userInfo__password,
        u.name as userInfo__name,
        u.photo as userInfo__photo,
        r.diskname as userInfo__photoSrc,
        u.sex as userInfo__sex,
        u.birthday as userInfo__birthday,
        u.idcard as userInfo__idcard,
        u.identity as userInfo__identity,
        u.phone as userInfo__phone,
        u.email as userInfo__email,
        u.role_id as userInfo__role_id,
        u.org_id as userInfo__org_id,
        u.org_ids as userInfo__org_ids,
      
        d.id as deviceInfo__id,
        d.name as deviceInfo__name,
        d.keycode as deviceInfo__keycode,
        d.position as deviceInfo__position,
        d.groups as deviceInfo__groups,
        d.comment as deviceInfo__comment,
        d.classify as deviceInfo__classify,
          
        dp.id as partInfo__id,
        dp.device as partInfo__device,
        dp.name as partInfo__name,
        dp.keycode as partInfo__keycode,
        dp.comment as partInfo__comment,
        
        dpo.id as operationInfo__id,
        dpo.name as operationInfo__name,
        dpo.keycode as operationInfo__keycode,
        
        
        ts.id as settingInfo__id,
        ts.train as settingInfo__train,
        ts.type as settingInfo__type,
        ts.group as settingInfo__group,
        ts.device as settingInfo__device,
        ts.rule as settingInfo__rule,
        ts.user as settingInfo__user,
        ts.teacher as settingInfo__teacher
      from
        train_operation_log tol
      left join subject_step ss on
      ss.id = tol.step
      left join train_settings ts on
        ts.id = tol.setting
      left join user u on
      u.id = ts.user
      left join resource r on
      r.id = u.photo
      left join device d on
        d.id = ss.device
      left join device_parts dp 
        on
      dp.keycode = ss.part and dp.device = ss.device
      left join device_part_operation dpo on
      dpo.keycode = ss.operation
    where tol.train = ?`,
      [trainId],
    );
    const formatRes = formatQueryRes(res, 'tol');
    return formatRes;
  }
  /**
   * 获取某个训练设置信息
   * @param id 训练ID
   * @returns
   */
  async deatils(id: number) {
    return this.TrainRep.createQueryBuilder('train')
      .andWhere({ delFlag: 0, id })
      .leftJoinAndMapOne(
        'train.subjectInfo',
        Subject,
        'subject',
        'subject.id=train.subject',
      )
      .leftJoinAndMapMany(
        'train.settings',
        TrainSettings,
        'ts',
        'ts.train = train.id',
      )
      .leftJoinAndMapOne('ts.deviceInfo', Device, 'd', 'd.id = ts.device')
      .leftJoinAndMapOne(
        'ts.ip',
        DeviceIP,
        'ip',
        'ip.device = ts.device and ip.group = ts.group',
      )
      .leftJoinAndMapOne('ts.userInfo', User, 'u', 'u.id = ts.user')
      .getOne();
  }
  /**
   * 记录步骤
   * @param id 步骤ID
   * @param {UpdateStepLogDto} data  步骤数据
   */
  async logStep(id: number, data: UpdateStepLogDto) {
    const thisStep = await this.TOLogsRep.findOneByOrFail({ id: +id });
    // 如果步骤已经标记为错误，则不能重新标记为正确，但是可以重新标记为未开始（用于步骤回退）
    if (+thisStep.status == 2 && +data.status == 1) {
      data.status = 2;
    }
    return await this.TOLogsRep.update(id, data);
  }
  async paging(paging: PaginationDTO) {
    const res: any = await this.TrainRep.createQueryBuilder('train')
      .andWhere({
        delFlag: 0,
      })
      .orderBy('train.id', 'DESC')
      .skip((paging.currentPage - 1) * paging.pageSize)
      .take(paging.pageSize)
      .getManyAndCount();
    return {
      data: res[0],
      total: res[1],
    };
  }
  async findAll(status: number) {
    const whereAnd = { delFlag: 0 };
    if (status !== undefined && status !== null && !Number.isNaN(status))
      whereAnd['status'] = status;
    return await this.TrainRep.find({
      where: whereAnd,
    });
  }
  async update(id: number, updateClassifyDto: UpdateTrainDto) {
    return this.TrainRep.update(id, updateClassifyDto);
  }
  async remove(id: number) {
    if (isNaN(+id) || !id) throw '缺少id';
    return await this.TrainRep.query(
      `
    delete train,tol,tsl,ts from  train 
    left join train_operation_log tol on tol.train  = train.id
    left join train_score_log tsl on tsl.train  = train.id 
    left join train_settings  ts on ts.train  = train.id 
    where train.id= ?
    `,
      [id],
    );
  }
  async removeAll() {
    const trainList = await this.TrainRep.find();
    if (!trainList) throw '无训练数据';
    const ids = trainList.map((i) => i.id);
    return await this.TrainRep.query(
      `
    delete train,tol,tsl,ts from  train 
    left join train_operation_log tol on tol.train  = train.id
    left join train_score_log tsl on tsl.train  = train.id 
    left join train_settings  ts on ts.train  = train.id 
    where train.id in (?)
    `,
      [ids],
    );
  }
  /**
   * 保存成绩数据
   * @param train 训练ID
   * @param datas 成绩数据
   */
  async logScore(train: number, datas: LogScoreDto[]) {
    await this.TrainScoreLogRep.delete({ train });
    return await this.TrainScoreLogRep.insert(datas);
  }
  /**
   *获取成绩
   * @param train 训练ID
   */
  async getScore(train: number) {
    return await this.TrainScoreLogRep.createQueryBuilder('score')
      .andWhere({ train })
      .leftJoinAndMapOne(
        'score.settingInfo',
        TrainSettings,
        'ts',
        'ts.id = score.setting',
      )
      .leftJoinAndMapOne('score.userInfo', User, 'u', 'u.id = ts.user')
      .getMany();
  }
}

export class CreateTrainDto extends CreateDto {
  @ApiProperty({
    description:
      '训练名称存储到数据库时自动增加时间前缀如：yyyy-MM-dd hh:mm-训练名称',
  })
  @IsNotEmpty()
  name: string;

  @ApiProperty({ description: '训练类型' })
  @IsNotEmpty()
  type: number;
  @ApiProperty({ description: '训练科目' })
  @IsNotEmpty()
  subject: number;

  @ApiProperty({ description: '训练描述', default: '', required: false })
  comment: string;

  @ApiProperty({ description: '开启引导', required: false, default: 0 })
  @IsNotEmpty()
  guide: number;

  @ApiProperty({ description: '训练时长', default: 0 })
  @IsNotEmpty()
  duration: number;

  @ApiProperty({ description: '评判规则', default: '{}' })
  @IsNotEmpty()
  rule: string;

  @ApiProperty({ description: '训练状态', default: 0 })
  status: number;

  @ApiProperty({
    description: '人员设置 rule(考核规则) teacher(是否为指导岗)',
    default: [
      [{ group: 1, device: 1, rule: 0, user: '用户ID', teacher: 0 }],
      [{ group: 2, device: 1, rule: 0, user: '用户ID', teacher: 0 }],
    ],
  })
  @IsNotEmpty()
  setting?: {
    group: number;
    device: number;
    rule: number;
    user: string;
    teacher: number;
  }[][];
}

export class UpdateTrainDto extends CreateDto {
  @ApiProperty({ description: '训练名称' })
  name: string;

  @ApiProperty({ description: '训练类型' })
  type: number;
  @ApiProperty({ description: '训练科目' })
  subject: number;

  @ApiProperty({ description: '训练描述', default: '' })
  comment: string;
  @ApiProperty({ description: '开启引导', default: 0 })
  guide: number;

  @ApiProperty({ description: '训练时长', default: 0 })
  duration: number;

  @ApiProperty({ description: '评判规则', default: '{}' })
  rule: string;

  @ApiProperty({ description: '训练状态', default: 0 })
  status: number;

  @ApiProperty({
    description: '人员设置 rule(考核规则) teacher(是否为指导岗)',
    default: [
      [{ group: 1, device: 1, rule: 0, user: '用户ID', teacher: 0 }],
      [{ group: 2, device: 1, rule: 0, user: '用户ID', teacher: 0 }],
    ],
  })
  setting?: {
    group: number;
    device: number;
    rule: number;
    user: string;
    teacher: number;
  }[][];
}

export class UpdateStepLogDto extends CreateDto {
  @ApiProperty({
    description: '状态 0 等待执行 1成功 2错误 修改为错误后将不能再修改为正确',
    required: false,
  })
  status: number;
  @ApiProperty({
    description: '用时',
    required: false,
  })
  duration: number;
  @ApiProperty({
    description: '中途暂停了多久',
    required: false,
  })
  pause: number;
}
export class LogScoreDto extends CreateDto {
  @ApiProperty({
    description: '训练ID',
  })
  @IsNotEmpty()
  train: number;

  @ApiProperty({
    description: '操作设置ID',
  })
  @IsNotEmpty()
  setting: number;

  @ApiProperty({
    description: '用时毫秒',
  })
  @IsNotEmpty()
  duration: number;

  @ApiProperty({
    description: '分数',
  })
  @IsNotEmpty()
  score: number;
}

export class ProxyFetchDto {
  @ApiProperty({
    description: '请求方法，使用大写',
    required: false,
    default: 'GET',
  })
  method: string;
  @ApiProperty({ description: '请求地址', required: true })
  url: string;
  @ApiProperty({ description: '请求体', required: false })
  data: object;
  @ApiProperty({
    description: '基本请求验证',
    required: false,
    default: { username: 'string', password: 'string' },
  })
  auth: { username: string; password: string };
}
