package cn.palmte.work.service;

import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;

import cn.palmte.work.ErrorMessageException;
import cn.palmte.work.config.activiti.ActConstant;
import cn.palmte.work.config.activiti.ActProjectTypeEnum;
import cn.palmte.work.model.Admin;
import cn.palmte.work.model.AdminRepository;
import cn.palmte.work.model.Dept;
import cn.palmte.work.model.DeptRepository;
import cn.palmte.work.model.ProcurementType;
import cn.palmte.work.model.Project;
import cn.palmte.work.model.ProjectBudgetCostDetail;
import cn.palmte.work.model.ProjectRepository;
import cn.palmte.work.model.enums.ProcessStatus;
import cn.palmte.work.model.enums.ProcessType;
import cn.palmte.work.model.process.BudgetPurchaseAmount;
import cn.palmte.work.model.process.BudgetPurchaseDetail;
import cn.palmte.work.model.process.ProcurementContract;
import cn.palmte.work.model.process.ProcurementDetail;
import cn.palmte.work.model.process.ProjectProcess;
import cn.palmte.work.model.process.QueryProject;
import cn.palmte.work.model.process.SaleContract;
import cn.palmte.work.model.process.SupplierMaterial;
import cn.palmte.work.model.process.form.SaleContractDetailForm;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

/**
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
 * @since 2.0 2022/12/23 09:39
 */
@Service
@RequiredArgsConstructor
public class ProjectProcessService {

  private final JdbcTemplate jdbcTemplate;
  private final EntityManager entityManager;
  private final ProjectService projectService;
  private final ProjectInstanceService projectInstanceService;

  private final DeptRepository deptRepository;
  private final AdminRepository userRepository;
  private final ProjectRepository projectRepository;

  public List<Project> queryProjects(String q, ProcessType processType, Admin loginUser) {
    List<Project> projects = Collections.emptyList();
    if (processType == ProcessType.procurement_contract) {
      projects = projectRepository.findBudgetPassedProjects(q);
    }
    else if (processType == ProcessType.sale_contract) {
      List<QueryProject> queryProjects = jdbcTemplate.query(
              "select distinct p.id, p.name, p.project_no projectNo, pp.process_type processType,p.creator_id creatorId " +
                      " from project p left join project_process pp on p.id = pp.project_id " +
                      "where (p.`status` > 5 or (p.`status` = 5 and p.approve_status_budget = 2)) " +
                      "  and (p.`project_no` like concat('%', ?, '%') or p.`name` like concat('%', ?, '%'))",
              new Object[] { q, q },
              BeanPropertyRowMapper.newInstance(QueryProject.class));

      MultiValueMap<Integer, QueryProject> idMap = new LinkedMultiValueMap<>();

      for (QueryProject queryProject : queryProjects) {
        idMap.computeIfAbsent(queryProject.getId(), s -> new ArrayList<>())
                .add(queryProject);
      }

      projects = queryProjects.stream().filter(queryProject -> {
        List<QueryProject> projectList = idMap.get(queryProject.getId());
        return projectList.stream().noneMatch(this::isSaleContract);
      }).map(queryProject -> {
        Project project = new Project();
        project.setId(queryProject.getId());
        project.setName(queryProject.getName());
        project.setProjectNo(queryProject.getProjectNo());
        project.setCreatorId(queryProject.getCreatorId());
        return project;
      }).collect(Collectors.toList());
    }
    return projectService.visibleProjects(projects, loginUser);
  }

  private boolean isSaleContract(QueryProject queryProject) {
    return queryProject.getProcessType() == ProcessType.sale_contract;
  }

  public BigDecimal getContractAmount(int id) {
    Map<String, Object> map = jdbcTemplate.queryForMap(
            "select ifnull(sum(amount * price), 0) contractAmount from project_budget_income_detail where project_id = ?", id);
    return (BigDecimal) map.values().iterator().next();
  }

  @Data
  static class DeptReturnValue {

    private int id;
    private int leaderId;

    private String leaderName;

    private String name;

    private List<DeptReturnValue> children;

    public void addChildren(DeptReturnValue child) {
      if (children == null) {
        children = new ArrayList<>();
      }
      children.add(child);
    }
  }

  public List<DeptReturnValue> filterDept() {
    List<Dept> deptList = deptRepository.findEnable();
    List<Dept> level1 = filterByLevel(deptList, 1);
    List<Dept> level2 = filterByLevel(deptList, 2);
    List<Dept> level3 = filterByLevel(deptList, 3);

    List<DeptReturnValue> returnValues = new ArrayList<>();
    for (Dept dept : level1) {
      // 只需要一级领导
      Admin leader = userRepository.getAdminById(dept.getManagerId());
      String leaderName = leader.getRealName();
      DeptReturnValue returnValue = createReturnValue(dept, leaderName);
      for (Dept dept2 : level2) {
        if (Objects.equals(dept2.getParentId(), dept.getId())) {
          DeptReturnValue returnValue2 = createReturnValue(dept2, leaderName);
          for (Dept dept3 : level3) {
            if (Objects.equals(dept3.getParentId(), dept2.getId())) {
              returnValue2.addChildren(createReturnValue(dept3, leaderName));
            }
          }
          returnValue.addChildren(returnValue2);
        }
      }
      returnValues.add(returnValue);
    }
    return returnValues;
  }

  private static List<Dept> filterByLevel(List<Dept> deptList, int level) {
    ArrayList<Dept> ret = new ArrayList<>();
    for (Dept dept : deptList) {
      if (Objects.equals(dept.getLevel(), level)) {
        ret.add(dept);
      }
    }
    return ret;
  }

  private DeptReturnValue createReturnValue(Dept dept, String leaderName) {
    DeptReturnValue returnValue = new DeptReturnValue();

    returnValue.setId(dept.getId());
    returnValue.setName(dept.getName());
    returnValue.setLeaderId(dept.getManagerId());
    returnValue.setLeaderName(leaderName);
    return returnValue;
  }

  /**
   * 更新流程 审批人，和状态
   *
   * @param processId 流程Id
   * @param auditId 审批人ID 可以为空
   * @param status 流程状态 可以为空，为空的时候不修改
   */
  public void updateAudit(int processId, ProcessStatus status, List<Integer> auditId) {
    if (CollectionUtils.isEmpty(auditId)) {
      if (status != null) {
        jdbcTemplate.update("update project_process set current_audit=?,current_audit_id=?,status=? where id=?",
                null, null, status.getValue(), processId);
      }
      else {
        jdbcTemplate.update("update project_process set current_audit=?,current_audit_id=?  where id=?",
                null, null, processId);
      }
    }
    else {
      String currentAudit = getCurrentAudit(auditId);
      String currentAuditId = auditId.stream().map(String::valueOf).collect(Collectors.joining(","));
      if (status != null) {
        jdbcTemplate.update("update project_process set current_audit=?,current_audit_id=?,status=? where id=?",
                currentAudit, currentAuditId, status.getValue(), processId);
      }
      else {
        jdbcTemplate.update("update project_process set current_audit=?,current_audit_id=?  where id=?", currentAudit, currentAuditId, processId);
      }
    }
  }

  private String getCurrentAudit(List<Integer> auditId) {
    TypedQuery<Admin> query = entityManager.createQuery("from Admin where id in (:ids)", Admin.class);
    query.setParameter("ids", auditId);
    List<Admin> resultList = query.getResultList();
    return resultList.stream().map(Admin::getRealName)
            .collect(Collectors.joining(","));
  }

  /**
   * 只更新状态
   */
  public void updateProcessStatus(int processId, ProcessStatus status) {
    jdbcTemplate.update("update project_process set `status`=? where id=?", status.getValue(), processId);
  }

  /**
   * 撤回流程
   *
   * @param processId 撤回的ID
   */
  @Transactional
  public void revoke(int processId) {
    jdbcTemplate.update("update project_process set current_audit=?, current_audit_id=?, `status`=? where id=?",
            null, null, ProcessStatus.draft.getValue(), processId);
    ProjectProcess process = obtain(processId);
    if (process.getProcessType() == ProcessType.procurement_contract) {
      // 撤回的时候要删除对应的采购数量
      List<BudgetPurchaseAmount> purchaseAmount = getProcessPurchaseAmount(processId);
      for (BudgetPurchaseAmount amount : purchaseAmount) {
        amount.setSubmit(false);
        entityManager.merge(amount);
      }
    }
  }

  /**
   * 根据流程ID查询销售合同
   *
   * @param processId 流程ID
   * @return 销售合同
   */
  public SaleContract findSaleContract(int processId) {
    TypedQuery<SaleContract> query = entityManager.createQuery(
            "from SaleContract where processId=:processId", SaleContract.class);
    query.setParameter("processId", processId);
    return query.getSingleResult();
  }

  /**
   * 根据流程ID查询采购合同
   *
   * @param processId 流程ID
   * @return 采购合同
   */
  public ProcurementContract findProcurementContract(int processId) {
    TypedQuery<ProcurementContract> query = entityManager.createQuery(
            "from ProcurementContract where processId=:processId", ProcurementContract.class);
    query.setParameter("processId", processId);
    return query.getSingleResult();
  }

  public List<SupplierMaterial> getSupplierMaterials(int processId) {
    TypedQuery<SupplierMaterial> query = entityManager.createQuery(
            "from SupplierMaterial where processId=:processId", SupplierMaterial.class);
    query.setParameter("processId", processId);
    return query.getResultList();
  }

  /**
   * 获取 采购清单
   *
   * @param projectId 项目ID
   * @param processId 流程ID 一般用于获取详情
   */
  public List<ProcurementDetail> getProcurementDetails(int projectId, Integer processId) {
    ArrayList<ProcurementDetail> ret = new ArrayList<>();
    if (processId != null) {
      // 根据流程获取
      List<BudgetPurchaseAmount> purchaseAmount = getProcessPurchaseAmount(processId);
      for (BudgetPurchaseAmount amount : purchaseAmount) {
        ProjectBudgetCostDetail costDetail = getCostDetailById(amount.getBudgetCostId());
        ProcurementDetail detail = new ProcurementDetail();
        BeanUtils.copyProperties(costDetail, detail);

        Integer amountId = amount.getId();
        // 找对应 预算采购明细的数量记录 的采购详情
        List<BudgetPurchaseDetail> purchaseDetails = getBudgetPurchaseDetails(amountId);
        detail.setPurchaseDetails(purchaseDetails);
        detail.setBudgetCostId(costDetail.getId());
        detail.setCategory(getCategory(costDetail));

        detail.setAmountId(amountId);
        BigDecimal amountAlready = getAmountAlready(amount.getBudgetCostId());
        detail.setAmountAlready(amountAlready);
        BigDecimal allAmount = amount.getAmount();
        if (allAmount == null) {
          allAmount = costDetail.getAmount();
        }
        detail.setAmountLeft(allAmount.subtract(amountAlready));
        detail.setAmountCurrent(amount.getAmountCurrent());
        ret.add(detail);
      }
    }
    else {
      List<ProjectBudgetCostDetail> costDetails = getCostDetails(projectId);
      for (ProjectBudgetCostDetail costDetail : costDetails) {
        ProcurementDetail detail = new ProcurementDetail();
        BeanUtils.copyProperties(costDetail, detail);
        // 可能为 0
        BigDecimal amountAlready = getAmountAlready(costDetail.getId());
        detail.setAmountAlready(amountAlready);

        BigDecimal allAmount = costDetail.getAmount();
        detail.setAmountLeft(allAmount.subtract(amountAlready));
        detail.setAmount(allAmount);

        // TODO 查询太频繁
        detail.setCategory(getCategory(costDetail));
        detail.setBudgetCostId(costDetail.getId());
        ret.add(detail);
      }
    }
    return ret;
  }

  private String getCategory(ProjectBudgetCostDetail costDetail) {
    int category = costDetail.getCategory();
    TypedQuery<ProcurementType> query = entityManager.createQuery(
            "from ProcurementType where id=:category and isDeleted=0 and enabled=1", ProcurementType.class);
    query.setParameter("category", category);
    try {
      ProcurementType procurementType = query.getSingleResult();
      return procurementType.getName();
    }
    catch (NoResultException e) {
      return String.valueOf(category);
    }
  }

  private List<BudgetPurchaseDetail> getBudgetPurchaseDetails(int amountId) {
    TypedQuery<BudgetPurchaseDetail> query = entityManager.createQuery(
            "from BudgetPurchaseDetail where amountId=:amountId", BudgetPurchaseDetail.class);
    query.setParameter("amountId", amountId);
    return query.getResultList();
  }

  private ProjectBudgetCostDetail getCostDetailById(int budgetCostId) {
    return entityManager.find(ProjectBudgetCostDetail.class, budgetCostId);
  }

  private List<ProjectBudgetCostDetail> getCostDetails(int projectId) {
    TypedQuery<ProjectBudgetCostDetail> query = entityManager.createQuery(
            "from ProjectBudgetCostDetail where projectId=:projectId", ProjectBudgetCostDetail.class);
    query.setParameter("projectId", projectId);
    return query.getResultList();
  }

  /**
   * 获取采购合同流程所有的 预算采购明细的数量记录
   *
   * @param processId 合同流程
   * @return 采购合同流程预算采购明细的数量记录 列表
   */
  public List<BudgetPurchaseAmount> getProcessPurchaseAmount(Integer processId) {
    TypedQuery<BudgetPurchaseAmount> amountQuery = entityManager.createQuery(
            "from BudgetPurchaseAmount where processId=:processId", BudgetPurchaseAmount.class);
    amountQuery.setParameter("processId", processId);
    try {
      return amountQuery.getResultList();
    }
    catch (NoResultException e) {
      return new ArrayList<>();
    }
  }

  public BudgetPurchaseAmount getPurchaseAmount(int budgetCostId, Integer processId) {
    TypedQuery<BudgetPurchaseAmount> amountQuery = entityManager.createQuery(
            "from BudgetPurchaseAmount where budgetCostId=:budgetCostId and processId=:processId", BudgetPurchaseAmount.class);
    amountQuery.setParameter("budgetCostId", budgetCostId);
    amountQuery.setParameter("processId", processId);
    try {
      return amountQuery.getSingleResult();
    }
    catch (NoResultException e) {
      return null;
    }
  }

  public List<BudgetPurchaseAmount> getPurchaseAmountList(int budgetCostId) {
    TypedQuery<BudgetPurchaseAmount> amountQuery = entityManager.createQuery(
            "from BudgetPurchaseAmount where budgetCostId=:budgetCostId", BudgetPurchaseAmount.class);
    amountQuery.setParameter("budgetCostId", budgetCostId);
    return amountQuery.getResultList();
  }

  public List<BudgetPurchaseDetail> getProcessPurchaseDetail(Integer processId) {
    TypedQuery<BudgetPurchaseDetail> amountQuery = entityManager.createQuery(
            "from BudgetPurchaseDetail where processId=:processId", BudgetPurchaseDetail.class);
    amountQuery.setParameter("processId", processId);
    try {
      return amountQuery.getResultList();
    }
    catch (NoResultException e) {
      return new ArrayList<>();
    }
  }

  /**
   * 计算当前已经提交的采购数量
   */
  public BigDecimal getAmountAlready(int budgetCostId) {
    return getPurchaseAmountList(budgetCostId).stream()
            .filter(BudgetPurchaseAmount::isSubmitted)
            .map(BudgetPurchaseAmount::getAmountCurrent)
            .filter(Objects::nonNull)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
  }

  public ProjectProcess getById(int id) {
    return entityManager.find(ProjectProcess.class, id);
  }

  public ProjectProcess obtain(int id) {
    ProjectProcess projectProcess = getById(id);
    if (projectProcess == null) {
      throw ErrorMessageException.failed("流程不存在");
    }
    return projectProcess;
  }

  /**
   * 更新 销售合同 的 质保期
   */
  public void updateIncomeDetails(List<SaleContractDetailForm> incomeDetails) {
    if (!CollectionUtils.isEmpty(incomeDetails)) {
      jdbcTemplate.batchUpdate("update project_budget_income_detail set expiration_date =? where id =? ",
              new BatchPreparedStatementSetter() {

                @Override
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                  SaleContractDetailForm detailForm = incomeDetails.get(i);
                  ps.setString(1, detailForm.getExpirationDate());
                  ps.setInt(2, detailForm.getId());
                }

                @Override
                public int getBatchSize() {
                  return incomeDetails.size();
                }
              });
    }
  }

  public BigDecimal getProjectRepaidAmount(int projectId) {
    Map<String, Object> map = jdbcTemplate.queryForMap(
            "select ifnull(sum(underwritten_plan), 0) repaidAmount from project_budget_plan_detail where project_id = ?", projectId);
    return (BigDecimal) map.values().iterator().next();
  }

  /**
   * 发起流程审批
   */
  public void startAuditProgress(ProjectProcess entity) {
    HashMap<String, Object> variables = new HashMap<>();
    Project project = projectRepository.findById(entity.getProjectId());
    // 是否垫资
    variables.put("isPrepaid", isProjectPrepaid(project));
    // 垫资金额
    BigDecimal repaidAmount = getProjectRepaidAmount(entity.getProjectId());
    variables.put("repaidAmount", repaidAmount);
    // 合同金额
    variables.put("contractAmount", project.getContractAmount() == null ? 0 : project.getContractAmount());
    // 项目类型
    variables.put("projectType", project.getType());
    // 合作类型
    variables.put("cooperationType", project.getCooperateType() == null ? 0 : project.getCooperateType());
    // 部门领导ID
    variables.put(ActConstant.DEPT_LEADER_ID, entity.getApplyDeptLeaderId());

    startAuditProgress(entity, variables);
  }

  /**
   * 是否垫资
   */
  public boolean isProjectPrepaid(Project project) {
    return BigDecimal.ZERO.equals(getProjectRepaidAmount(project.getId()));
  }

  /**
   * 是否垫资
   */
  public boolean isProjectPrepaid(BigDecimal repaidAmount) {
    return BigDecimal.ZERO.compareTo(repaidAmount) < 0;
  }

  /**
   * 发起流程审批
   */
  @SneakyThrows
  public void startAuditProgress(ProjectProcess entity, Map<String, Object> variables) {
    if (entity.getStatus() == ProcessStatus.to_be_audit) {
      switch (entity.getProcessType()) {
        case sale_contract:
          projectInstanceService.startProcessByProjectId(entity.getId(), ActProjectTypeEnum.SALE_CONTRACT, variables);
          break;
        case procurement_contract:
          projectInstanceService.startProcessByProjectId(entity.getId(), ActProjectTypeEnum.BUSINESS_PURCHASE, variables);
          break;
        default:
          throw new UnsupportedOperationException("还不支持");
      }
    }
  }

}
