package net.northking.cctp.attachment.api.service;

import com.hzbank.testteam.autotest.dependencies.baseDefineDependency.constants.BaseDefineDependencyConstants;
import com.hzbank.testteam.autotest.dependencies.baseDefineDependency.exception.InvalidException;
import com.hzbank.testteam.autotest.dependencies.baseDefineDependency.vo.ResponseVO;
import net.northking.cctp.attachment.constant.FileConstant;
import net.northking.cctp.attachment.constant.ScriptConstant;
import net.northking.cctp.attachment.db.entity.Attachment;
import net.northking.cctp.attachment.db.service.AttachmentService;
import net.northking.cctp.attachment.dto.ChunkFiles;
import net.northking.cctp.attachment.dto.FileMessage;
import net.northking.cctp.common.enums.FileBusinessTypeEnum;
import net.northking.cctp.common.exception.PlatformRuntimeException;
import net.northking.cctp.common.http.ResultWrapper;
import net.northking.cctp.common.s3.ContentType;
import net.northking.cctp.common.s3.NKFile;
import net.northking.cctp.common.s3.SimpleStorageService;
import net.northking.cctp.common.security.authentication.NKSecurityContext;
import org.apache.commons.collections4.ListUtils;
import org.lokra.seaweedfs.exception.SeaweedfsFileNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 附件下载服务实现类
 *
 * @author : xiusong.xie
 * @version : 1.0
 */
@Service
@RefreshScope
public class AttachFileServiceImpl implements AttachFileService
{
  private static final Logger logger = LoggerFactory.getLogger(AttachFileServiceImpl.class);

  @Autowired
  private AttachmentService attachmentService;

  @Autowired
  private SimpleStorageService storageService;

  @Autowired
  private FileService fileService;

  @Value("${nk.clearAttachmentDay}")
  private Long clearAttachmentDay;

  @Value("${atu.plan.planReportSaveDay}")
  private Long planReportSaveDay;

  @Override
  public void download(String fileId, HttpServletResponse response, HttpServletRequest request)
  {
    String tenantId = NKSecurityContext.getTenantId();

    if (StringUtils.isEmpty(fileId))
    {
      response.setStatus(HttpStatus.NOT_FOUND.value());
      return;
    } else if (StringUtils.isEmpty(tenantId))
    {
      response.setStatus(HttpStatus.UNAUTHORIZED.value());
    }

    String fId = decodeFileId(fileId);
    String[] args = splitFileId(fId);

    download(tenantId, args[1], response, request);
  }

  @Override
  public void downloadPub(String fileId, HttpServletResponse response, HttpServletRequest request)
  {
    String fId = decodeFileId(fileId);
    String[] args = splitFileId(fId);
    download(args[0], args[1], response, request);
  }

  private void download(String tenantId, String objectId, HttpServletResponse response, HttpServletRequest request)
  {
    if (StringUtils.isEmpty(tenantId))
    {
      response.setStatus(HttpStatus.NOT_FOUND.value());
      return;
    }
    String id = objectId;
    // 判断objectId是id还是storagePath，如果是id，那么只有一个/
    if (objectId.lastIndexOf("/") > 0) {
      Attachment attachment = attachmentService.queryByStoragePath(tenantId, objectId); // 通过storagePath查询附件的id
      if (attachment != null) {
        id = attachment.getId();
      }
    }
    Attachment attachment = attachmentService.findByPK(tenantId, id);
    boolean check = false;
    int i=0;
    while (i<10){
      if(attachment==null){
        try {
          Thread.sleep(500);
        } catch (Exception e) {
          logger.error("等待报错",e);
        }
        attachment = attachmentService.findByPK(tenantId, id);
      }else{
        check=true;
        break;
      }
      i++;
    }
    if (!check) {
      response.setStatus(HttpStatus.NOT_FOUND.value());
      return;
    }else{
      if(!StringUtils.isEmpty(attachment.getNewFile())){
        attachment.setId(attachment.getNewFile());
      }
    }

    String filename;
    filename = UriUtils.encode(attachment.getFileName(), FileConstant.URL_ENCODING);    // 使用UriUtils处理特殊字符，jieying.li 20220317
    response.setContentType(ContentType.Application_Octet_Stream.getCode());
    response.setHeader(FileConstant.CONTENT_DISPOSITION, FileConstant.FILENAME_ATTACHMENT + filename);

    // 处理如果请求头带有Range属性，大概率是视频，chrome浏览器需求添加相对应的响应头，用于进度条的拖动
    if (!StringUtils.isEmpty(request.getHeader(FileConstant.RANGE))) {
      String range = request.getHeader(FileConstant.RANGE);
      String rangIdx = range.substring(range.indexOf(FileConstant.EQUAL) + 1, range.length() - 1);
      response.setHeader(FileConstant.CONTENT_RANGE, String.valueOf(Long.parseLong(rangIdx) + (attachment.getLength() - 1)));
      response.setHeader(FileConstant.ACCEPT_RANGES, FileConstant.BYTES);
      response.setContentLength(Math.toIntExact(attachment.getLength()));
    }

    byte[] buffer = new byte[4096];
    int length;
    try (
            InputStream inputStream = storageService.downloadAsStream(attachment.getTenantId(), attachment.getId());
            OutputStream outputStream = response.getOutputStream()
    )
    {
      while ((length = inputStream.read(buffer)) > 0)
      {
        outputStream.write(buffer, 0, length);
      }
      outputStream.flush();
    } catch (Exception e)
    {
      logger.error("下载本地文件失败！文件ID：" + attachment.getId(), e);
      response.setStatus(HttpStatus.NOT_FOUND.value());
    }
  }

  @Override
  public ResultWrapper<String> upload(HttpServletResponse response, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode)
  {
    ResultWrapper<String> resultWrapper = new ResultWrapper<>();
    String tenantId = NKSecurityContext.getTenantId();
    String userId = NKSecurityContext.getUserId();
    try
    {
      NKFile nkFile = storageService.upload(tenantId, multipartFile, objId, businessCode);

      Attachment attachment = new Attachment();
      BeanUtils.copyProperties(nkFile, attachment);
      attachment.setCreatedBy(userId);
      attachment.setCreatedTime(new Date());
      attachment.setObjId(objId);
      attachment.setBusinessCode(businessCode.getCode());
      attachment.setOverTime(businessCode.getOverTime());
      attachmentService.insert(attachment);

      String urlPath = tenantId + encodeFileId(nkFile.getId());
      resultWrapper.success(urlPath);
    } catch (Exception e)
    {
      logger.error("上传本地文件失败！", e);
      resultWrapper.fail(FileConstant.RESULT_WRAPPER_FAIL, e.getMessage());
    }
    return resultWrapper;
  }

  @Override
  public ResultWrapper<Map<String, Object>> uploadResource(HttpServletResponse response, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode, String attachId)
  {
    ResultWrapper<Map<String, Object>> resultWrapper = new ResultWrapper<>();
    resultWrapper.setData(new HashMap<>());
    String tenantId = NKSecurityContext.getTenantId();
    String userId = NKSecurityContext.getUserId();
    try
    {
      NKFile nkFile = storageService.upload(tenantId, multipartFile, objId, businessCode, attachId);

      Attachment attachment = new Attachment();
      BeanUtils.copyProperties(nkFile, attachment);
      attachment.setCreatedBy(userId);
      attachment.setCreatedTime(new Date());
      attachment.setObjId(objId);
      attachment.setBusinessCode(businessCode.getCode());
      attachment.setOverTime(businessCode.getOverTime());
      attachment.setStoragePath(attachId);
      attachmentService.insert(attachment);

      String urlPath = tenantId + encodeFileId(nkFile.getId());
      resultWrapper.getData().put(FileConstant.URL_PATH, urlPath);
      resultWrapper.getData().put(FileConstant.FILE_ID, attachment.getId());
      resultWrapper.setSuccess(true);
      return resultWrapper;
    } catch (Exception e)
    {
      logger.error("上传本地文件失败！", e);
      resultWrapper.fail(FileConstant.RESULT_WRAPPER_FAIL, e.getMessage());
    }

    return resultWrapper;
  }

  @Override
  public ResultWrapper<String> update(Map<String, String> file) {
    if(file.containsKey(FileConstant.FILE_ID)&& file.containsKey(FileConstant.NEW_FILE)){
      String id = splitFileId(decodeFileId(file.get(FileConstant.FILE_ID)))[1];
      String newFile = splitFileId(decodeFileId(file.get(FileConstant.NEW_FILE)))[1];
      String tenantId= splitFileId(decodeFileId(file.get(FileConstant.FILE_ID)))[0];

      LocalDateTime overTime = LocalDateTime.now().plusDays(planReportSaveDay);
      Attachment update = new Attachment();
      update.setId(id);
      // 更新过期时间
      update.setOverTime(Date.from(overTime.atZone(ZoneOffset.systemDefault()).toInstant()));
      update.setNewFile(newFile);
      update.setTenantId(tenantId);
      update.setTableSuffix(tenantId);
      attachmentService.updateByPrimaryKey(update);
      update.setId(id);
      attachmentService.updateAllNewFile(update);
    }
    return null;
  }

  @Override
  public ResultWrapper<FileMessage> uploadForApp(HttpServletResponse response, MultipartFile multipartFile) {
    ResultWrapper<FileMessage> resultWrapper = new ResultWrapper<>();
    String tenantId = NKSecurityContext.getTenantId();
    String userId = NKSecurityContext.getUserId();
    FileMessage fileMessage;
    try
    {
      FileBusinessTypeEnum appFile = FileBusinessTypeEnum.APP_FILE;
      NKFile nkFile = storageService.upload(tenantId, multipartFile, null, appFile);
      Attachment attachment = new Attachment();
      BeanUtils.copyProperties(nkFile, attachment);
      attachment.setBusinessCode(appFile.getCode());
      attachment.setOverTime(appFile.getOverTime());
      attachment.setCreatedBy(userId);
      attachment.setCreatedTime(new Date());

      attachmentService.insert(attachment);

      String urlPath = tenantId + encodeFileId(nkFile.getId());
      String suffixName = urlPath.substring(urlPath.lastIndexOf(".") + 1);

      try {
        if (ScriptConstant.FILE_APK.equals(suffixName.toLowerCase())) {
          fileMessage = fileService.uploadAndroidLargeApp(multipartFile,urlPath);
          resultWrapper.success(fileMessage);
        } else if (ScriptConstant.FILE_IPA.equals(suffixName.toLowerCase())) {
          fileMessage = fileService.uploadIosLargeApp(multipartFile,urlPath);
          resultWrapper.success(fileMessage);
        } else if (ScriptConstant.FILE_HAP.equals(suffixName.toLowerCase())) {
          fileMessage = fileService.uploadHarmonyOSLargeApp(multipartFile,urlPath);
          resultWrapper.success(fileMessage);
        } else {

        }
      }catch (PlatformRuntimeException e) {
        throw e;
      }

    } catch (Exception e)
    {
      logger.error("上传本地文件失败！", e);
      resultWrapper.fail(FileConstant.RESULT_WRAPPER_FAIL, e.getMessage());
    }
    return resultWrapper;
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public ResultWrapper<String> associatedFiles(String objectId, List<String> fileIds) {

    if (CollectionUtils.isEmpty(fileIds)) {
      throw new InvalidException("文件Id不能为空");
    }
    // 分批更新，每次500个
    List<List<String>> partition = ListUtils.partition(fileIds, 500);
    for (List<String> list : partition) {
      attachmentService.associatedFiles(objectId, list);
    }
    ResultWrapper<String> wrapper = new ResultWrapper<>();

    return wrapper.success("更新成功");
  }

  @Override
  public ResultWrapper<String> deleteFileByObjIdAndBusinessCode(String objectId,String businessCode) {
    Attachment query = new Attachment();
    query.setObjId(objectId);
    query.setBusinessCode(businessCode);
    query.setDeleted(false);
    query.setTenantId(NKSecurityContext.getTenantId());
    List<Attachment> attachmentList = attachmentService.query(query);
    for (Attachment attachment : attachmentList) {
      attachment.setObjId("-1");
      attachmentService.updateByPrimaryKey(attachment);
    }
    ResultWrapper<String> wrapper = new ResultWrapper<>();

    return wrapper.success("删除成功");
  }

  /**
   * 清除文件：
   * @return 异步响应
   */
    @Override
    public ResponseVO<String> clearAttachments() {
      new Thread(() -> {
        List<String> businessCodes = new ArrayList<>();
        businessCodes.add(FileBusinessTypeEnum.MOBILE_RECORD_SCREENSHOT.getCode());
        businessCodes.add(FileBusinessTypeEnum.PC_RECORD_SCREENSHOT.getCode());
        // 查询3天前的无用录屏截图
        LocalDate localDate = LocalDate.now().minusDays(3);
        List<Attachment> recordScreenshot = attachmentService.queryClearRecordScreenshot(businessCodes, localDate);
        LocalDate date = null;
        // 清除未关联数据
        if (clearAttachmentDay != null) {
          date= LocalDate.now().minusDays(clearAttachmentDay);
        } else {
          date = LocalDate.now().minusDays(3);
        }
        List<Attachment> query = attachmentService.queryByObjId("-1", date);
        recordScreenshot.addAll(query);
        // 清除过期数据
        List<Attachment> queryOverTime = attachmentService.queryByOverTime(LocalDateTime.now());
        recordScreenshot.addAll(queryOverTime);
        deleteFiles(recordScreenshot);

        while (true) {
          List<Attachment> attachments = attachmentService.queryByBusiness(LocalDate.now().minusDays(planReportSaveDay));
          if (CollectionUtils.isEmpty(attachments)){
            break;
          }
          deleteFiles(attachments);
          if (attachments.size() <= 500){
            break;
          }
        }
      }).start();
      ResponseVO<String> responseVO = new ResponseVO<>();
      responseVO.setCode(BaseDefineDependencyConstants.SYSTEM_RESPONSE_CODE_SUCCESS);
      responseVO.setMessage("正在删除文件");
      return responseVO;
    }

  private void deleteFiles(List<Attachment> attachments) {
    for (Attachment attachment : attachments) {
      logger.info("删除文件：{}", attachment.getId());
      String tenantId = NKSecurityContext.getTenantId();
      try {
        attachment.setTenantId(tenantId);
        storageService.delete(tenantId, attachment.getId());
        attachment.setObjId("-1");
        attachment.setDeleted(true);
        // 更新文件表
        attachmentService.updateByPrimaryKey(attachment);
      } catch (Exception e) {
        logger.error("文件删除失败",e);
        if (e.getCause() instanceof SeaweedfsFileNotFoundException){
          logger.error("文件在文件服务器未找到，需要删除");
          // 文件在文件服务器未找到，更新文件表
          attachment.setObjId("-1");
          attachment.setDeleted(true);
          attachmentService.updateByPrimaryKey(attachment);
        }
      }
    }

  }

  /**
   * 删除文件，根据objIds 将文件记录的objId为-1，由monster触发的定时任务负责删除
   * @param objectIds 关联对象的id集合
   * @return
   */
    @Override
    public ResultWrapper<String> deleteFileByObjIds(List<String> objectIds) {
      if (CollectionUtils.isEmpty(objectIds)) {
        throw new InvalidException("关联对象Id集合不能为空");
      }
      attachmentService.updateByObjIds(objectIds);
      ResultWrapper<String> wrapper = new ResultWrapper<>();
      return wrapper.success("删除成功");
    }

    @Override
    public ResponseVO<String> clearSceneTaskAttachments(String objId, String businessCode, String noDeleteFileId) {
      Attachment record = new Attachment();
      record.setObjId(objId);
      record.setBusinessCode(businessCode);
      record.setTenantId(NKSecurityContext.getTenantId());
      List<Attachment> query = attachmentService.query(record);
      List<String> deleteIds = query.stream().map(Attachment::getId).filter(id -> id.equals(noDeleteFileId)).collect(Collectors.toList());
      attachmentService.clearSceneTaskAttachments(deleteIds);
      ResponseVO<String> responseVO = new ResponseVO<>();
      responseVO.setCode(BaseDefineDependencyConstants.SYSTEM_RESPONSE_CODE_SUCCESS);
      responseVO.setMessage("标记待删除成功");
      return responseVO;
    }


    @Override
  public ResultWrapper<String> mergeChunkFiles(ChunkFiles chunkFiles) {
    ResultWrapper<String> resultWrapper = new ResultWrapper<>();
    String tenantId = NKSecurityContext.getTenantId();
    String userId = NKSecurityContext.getUserId();
    try
    {
      // 从redis获取文件相关信息
      int index = chunkFiles.getFileName().lastIndexOf(".");
      String suffixName = null;
      if(index>0){
        suffixName= chunkFiles.getFileName().substring(index + 1);
      }
      NKFile nkFile = storageService.mergeChunkFiles(tenantId, chunkFiles.getUploadId(), suffixName);
      Attachment attachment = new Attachment();
      BeanUtils.copyProperties(nkFile, attachment);
      attachment.setCreatedBy(userId);
      attachment.setCreatedTime(new Date());
      attachment.setFileName(chunkFiles.getFileName());
      attachment.setContentType(chunkFiles.getContentType());
      attachment.setLength(chunkFiles.getFileSize());
      attachmentService.insert(attachment);
      resultWrapper.success(tenantId + encodeFileId(attachment.getId()));
    } catch (Exception e)
    {
      logger.error("合并文件失败", e);
      resultWrapper.fail(FileConstant.RESULT_WRAPPER_FAIL, e.getMessage());
    }
    return resultWrapper;
  }

  @Override
  public ResultWrapper<Boolean> uploadChunkFile(MultipartFile multipartFile, String uploadId, String chunkIndex)
  {
    ResultWrapper<Boolean> resultWrapper = new ResultWrapper<>();
    String tenantId = NKSecurityContext.getTenantId();
    try
    {
      storageService.uploadChunk(tenantId, multipartFile,uploadId,chunkIndex);
      resultWrapper.success(true);
    } catch (Exception e)
    {
      logger.error("上传本地文件失败！", e);
      resultWrapper.fail(FileConstant.RESULT_WRAPPER_FAIL, e.getMessage());
    }
    return resultWrapper;
  }


  private String encodeFileId(String id)
  {
    return id.replace('/', '_');
  }

  private String decodeFileId(String id)
  {
    return id.replace('_', '/');
  }

  private String[] splitFileId(String id)
  {
    int idx = id.indexOf('/');
    String tenantId = id.substring(0, idx);
    String filedId = id.substring(idx);
    logger.debug("租户：{}， 附件id：{}", tenantId, id);
    return new String[]{tenantId, filedId};
  }


  @Override
  public ResultWrapper<String> delete(String fileId)
  {
    String tenantId = NKSecurityContext.getTenantId();
    String fId = decodeFileId(fileId);
    String[] args = splitFileId(fId);

    return delete(tenantId, args[1]);
  }


  private ResultWrapper<String> delete(String tenantId, String objectId)
  {
    ResultWrapper<String> resultWrapper = new ResultWrapper<>();

    if (StringUtils.isEmpty(tenantId))
    {
      logger.error("删除文件失败！租户ID为空。");
      return resultWrapper.fail(FileConstant.RESULT_WRAPPER_FAIL, "");
    }


    Attachment attachment = attachmentService.findByPK(tenantId, objectId);
    if (attachment != null)
    {
      try
      {
        storageService.delete(tenantId, objectId);
        attachment.setDeleted(true);
        attachmentService.updateByPrimaryKey(attachment);
        resultWrapper.success();
      } catch (Exception e)
      {
        logger.error("删除文件失败！", e);
        resultWrapper.fail(FileConstant.RESULT_WRAPPER_FAIL, e.getMessage());
      }
    }
    return resultWrapper;
  }
}
