package cn.palmte.work.controller.backend;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;

import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import cn.palmte.work.config.activiti.ActApproveTypeEnum;
import cn.palmte.work.config.activiti.ActProjectTypeEnum;
import cn.palmte.work.model.Admin;
import cn.palmte.work.model.DeptRepository;
import cn.palmte.work.model.Project;
import cn.palmte.work.model.ProjectBudgetIncomeDetail;
import cn.palmte.work.model.ProjectRepository;
import cn.palmte.work.model.ProjectTaskRecord;
import cn.palmte.work.model.enums.CooperationType;
import cn.palmte.work.model.enums.Enumerable;
import cn.palmte.work.model.enums.ProcessStatus;
import cn.palmte.work.model.enums.ProcessType;
import cn.palmte.work.model.enums.ProcurementMode;
import cn.palmte.work.model.enums.ProjectType;
import cn.palmte.work.model.enums.SealType;
import cn.palmte.work.model.process.ProcessAttachment;
import cn.palmte.work.model.process.ProcurementContract;
import cn.palmte.work.model.process.ProjectProcess;
import cn.palmte.work.model.process.ProjectProcessRepository;
import cn.palmte.work.model.process.SaleContract;
import cn.palmte.work.model.process.SealTypeArray;
import cn.palmte.work.model.process.form.ProcessQueryForm;
import cn.palmte.work.model.process.form.ProcessUpdateForm;
import cn.palmte.work.model.process.form.ProcessCreationForm;
import cn.palmte.work.service.ProjectBudgetService;
import cn.palmte.work.service.ProjectInstanceService;
import cn.palmte.work.service.ProjectProcessService;
import cn.palmte.work.service.ProjectTaskRecordService;
import cn.palmte.work.utils.InterfaceUtil;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;

/**
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
 * @since 1.0 2022/12/8 11:03
 */
@Controller
@RequiredArgsConstructor
@RequestMapping("/process")
public class ProcessController {
  static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

  private final DeptRepository deptRepository;
  private final ProjectRepository projectRepository;
  private final ProjectBudgetService projectBudgetService;

  private final JdbcTemplate jdbcTemplate;
  private final EntityManager entityManager;

  private final ProjectProcessRepository repository;
  private final ProjectInstanceService projectInstanceService;
  private final ProjectProcessService processService;
  private final ProjectTaskRecordService projectTaskRecordService;

  static class FormMetadata {
    // 部门

    // 印章类型
    public Map<String, String> sealTypes;

    // 税率
    public List<Integer> taxRate;
  }

  @ResponseBody
  @GetMapping("/form/metadata")
  public FormMetadata metadata() {
    FormMetadata metadata = new FormMetadata();
    metadata.sealTypes = Arrays.stream(SealType.values())
            .collect(Collectors.toMap(SealType::getDescription, Enum::name));
    metadata.taxRate = Arrays.asList(0, 1, 3, 4, 5, 6, 9, 10, 13);
    return metadata;
  }

  /**
   * 新建流程
   */
  @GetMapping("/new")
  public String newProcess(Model model) {
    model.addAttribute("sealTypes", SealType.values());
    model.addAttribute("procurementMode", ProcurementMode.values());
    model.addAttribute("taxRate", Arrays.asList(0, 1, 3, 4, 5, 6, 9, 10, 13));
    model.addAttribute("applyDeptSectorOptions", JSON.toJSONString(processService.filterDept()));
    return "/admin/business/process-new";
  }

  /**
   * 编辑流程
   */
  @GetMapping("/edit/{id}")
  public String editProcess(Model model, @PathVariable int id) {
    model.addAttribute("processId", id);
    model.addAttribute("sealTypes", SealType.values());
    model.addAttribute("procurementMode", ProcurementMode.values());
    model.addAttribute("taxRate", Arrays.asList(0, 1, 3, 4, 5, 6, 9, 10, 13));
    model.addAttribute("applyDeptSectorOptions", JSON.toJSONString(processService.filterDept()));
    return "/admin/business/process-edit";
  }

  /**
   * 已办流程
   */
  @GetMapping("/completed")
  public String completed(Model model) {
    model.addAttribute("processTypes", ProcessType.values());
    model.addAttribute("processStatus", ProcessStatus.values());
    return "/admin/business/process-completed";
  }

  /**
   * 待我审核
   */
  @GetMapping("/review")
  public String review(Model model) {
    model.addAttribute("processTypes", ProcessType.values());
    return "/admin/business/process-review";
  }

  @GetMapping("/detail/{id}")
  public String detail(@PathVariable int id, Model model) {
    ProjectProcess process = repository.findOne(id);
    model.addAttribute("process", process);

    Project project = projectRepository.findById(process.getProjectId());
    model.addAttribute("project", project);
    model.addAttribute("projectType", Enumerable.of(ProjectType.class, project.getType()).getDescription());
    model.addAttribute("cooperationType", Enumerable.of(CooperationType.class, project.getCooperateType()).getDescription());
    List<ProcessAttachment> attachments = JSONArray.parseArray(process.getAttachmentUri(), ProcessAttachment.class);
    model.addAttribute("attachments", attachments);
    model.addAttribute("isPrepaid", isPrepaid(project));
    BigDecimal repaidAmount = getRepaidAmount(id);
    model.addAttribute("repaidAmount", repaidAmount);

    if (process.getProcessType() != null) {
      switch (process.getProcessType()) {
        case sale_contract: {
          TypedQuery<SaleContract> query = entityManager.createQuery(
                  "from SaleContract where processId=:processId", SaleContract.class);
          query.setParameter("processId", process.getId());
          SaleContract contract = query.getSingleResult();
          model.addAttribute("contract", contract);
          break;
        }
        case procurement_contract: {
          TypedQuery<ProcurementContract> query = entityManager.createQuery(
                  "from ProcurementContract where processId=:processId", ProcurementContract.class);
          query.setParameter("processId", process.getId());
          ProcurementContract contract = query.getSingleResult();
          model.addAttribute("contract", contract);
          break;
        }
        default:
          throw new UnsupportedOperationException("还不支持");
      }
    }

    return "/admin/business/process-detail";
  }

  private BigDecimal getRepaidAmount(int id) {
    return processService.getProjectRepaidAmount(id);
  }

  /**
   * 搜索符合条件的项目,供给选择
   */
  @ResponseBody
  @GetMapping("/projects")
  public List<Map<String, Object>> query(@RequestParam String q) {
    return projectRepository.findBudgetPassedProjects(q)
            .stream()
            .map(project -> {
              HashMap<String, Object> map = new HashMap<>();
              map.put("id", project.getId());
              map.put("name", project.getName());
              return map;
            })
            .collect(Collectors.toList());
  }

  @Builder
  public static class ProjectReturnValue {
    public final String applyDate = LocalDate.now().format(formatter);
    public String projectNo;
    public Integer projectId;
    public String projectName;

    // 申请人
    public String applyPersonName;

    // 项目类型
    public String projectType;

    // 合作类型
    public String cooperationType;

    // 合同金额
    public BigDecimal contractAmount;

    public String terminalCustomer;

    public List<ProjectBudgetIncomeDetail> incomeDetails;

    // FIXME 垫资
    // 是否垫资
    public String isPrepaid;

    // 垫资金额
    public String repaidAmount;

    // 预算毛利率
    public BigDecimal budgetGrossMargin;

    public Object contract;
    public ProjectProcess process;
  }

  @ResponseBody
  @GetMapping("/projects/{id}")
  public ProjectReturnValue query(@PathVariable int id) {
    Project project = projectRepository.findById(id);
    Admin admin = InterfaceUtil.getAdmin();

    // project_budget_plan_detail 垫资金额计算

    BigDecimal repaidAmount = getRepaidAmount(id);
    // 可以在对应表数据查询 是否存在再启用
    List<ProjectBudgetIncomeDetail> incomeDetails = projectBudgetService.getBudgetIncomeDetail(project);
    return ProjectReturnValue.builder()
            .isPrepaid(isPrepaid(project))
            .projectId(project.getId())
            .incomeDetails(incomeDetails)
            .projectName(project.getName())
            .repaidAmount(repaidAmount + "元")
            .budgetGrossMargin(project.getGrossProfitMargin())
            .projectNo(project.getProjectNo())
            .applyPersonName(admin.getRealName())
            .contractAmount(project.getContractAmount())
            .terminalCustomer(project.getTerminalCustomer())
            .projectType(Enumerable.of(ProjectType.class, project.getType()).getDescription())
            .cooperationType(Enumerable.of(CooperationType.class, project.getCooperateType()).getDescription())
            .build();
  }

  private String isPrepaid(Project project) {
    return processService.isProjectPrepaid(project) ? "是" : "否";
  }

  @ResponseBody
  @GetMapping("/{id}")
  public ProjectReturnValue get(@PathVariable int id) {
    ProjectProcess process = processService.getById(id);
    Project project = projectRepository.findById(process.getProjectId());
    BigDecimal repaidAmount = getRepaidAmount(id);
    Object contract;
    switch (process.getProcessType()) {
      case sale_contract: {
        contract = processService.findSaleContract(process.getId());
        break;
      }
      case procurement_contract: {
        contract = processService.findProcurementContract(process.getId());
        break;
      }
      default:
        throw new UnsupportedOperationException("还不支持");
    }

    // 可以在对应表数据查询 是否存在再启用
    List<ProjectBudgetIncomeDetail> incomeDetails = projectBudgetService.getBudgetIncomeDetail(project);
    return ProjectReturnValue.builder()
            .process(process)
            .contract(contract)
            .isPrepaid(isPrepaid(project))
            .projectId(project.getId())
            .incomeDetails(incomeDetails)
            .projectName(project.getName())
            .repaidAmount(repaidAmount + "元")
            .budgetGrossMargin(project.getGrossProfitMargin())
            .projectNo(project.getProjectNo())
            .applyPersonName(process.getApplyPersonName())
            .contractAmount(project.getContractAmount())
            .terminalCustomer(project.getTerminalCustomer())
            .projectType(Enumerable.of(ProjectType.class, project.getType()).getDescription())
            .cooperationType(Enumerable.of(CooperationType.class, project.getCooperateType()).getDescription())
            .build();
  }

  // 销售合同流程

  @ResponseBody
  @PostMapping
  @Transactional(rollbackFor = Exception.class)
  public void post(@RequestBody @Valid ProcessCreationForm form) throws Exception {
    ProjectProcess entity = new ProjectProcess();
    BeanUtils.copyProperties(form, entity, "sealTypes", "applyDate", "attachments");
    entity.setApplyDate(LocalDate.parse(form.getApplyDate(), formatter));
    entity.setSealTypes(SealTypeArray.of(form.getSealTypes()));
    Admin admin = InterfaceUtil.getAdmin();
    entity.setApplyPersonId(admin.getId());
    entity.setAttachmentUri(JSON.toJSONString(form.getAttachments()));

    entityManager.persist(entity);

    if (entity.getProcessType() != null) {
      switch (entity.getProcessType()) {
        case sale_contract:
          SaleContract saleContract = SaleContract.from(form);
          saleContract.setProcessId(entity.getId());
          entityManager.persist(saleContract);
          break;
        case procurement_contract:
          ProcurementContract procurementContract = ProcurementContract.from(form);
          procurementContract.setProcessId(entity.getId());
          entityManager.persist(procurementContract);
          break;
        default:
          throw new UnsupportedOperationException("还不支持");
      }
    }

    processService.updateIncomeDetails(form.getIncomeDetails());

    if (form.getStatus() == ProcessStatus.to_be_audit) {
      // 发起流程
      processService.startAuditProgress(entity);
    }
  }

  @ResponseBody
  @PutMapping
  @Transactional(rollbackFor = Exception.class)
  public void update(@RequestBody @Valid ProcessUpdateForm form) throws Exception {
    ProjectProcess entity = processService.getById(form.getId());

    entity.setTaxRate(form.getTaxRate());
    entity.setContractNo(form.getContractNo());
    entity.setContractName(form.getContractName());

    entity.setSealTypes(SealTypeArray.of(form.getSealTypes()));
    entity.setApplyDept(String.join(",", form.getApplyDept()));
    entity.setAttachmentUri(JSON.toJSONString(form.getAttachments()));

    entityManager.merge(entity);

    switch (entity.getProcessType()) {
      case sale_contract: {
        SaleContract contract = processService.findSaleContract(form.getId());
        contract.setClientName(form.getClientName());
        contract.setPaymentTerms(form.getPaymentTerms());
        contract.setApplyPersonPhone(form.getApplyPersonPhone());
        entityManager.merge(contract);
        break;
      }
      case procurement_contract: {
        ProcurementContract contract = processService.findProcurementContract(form.getId());
        contract.setMode(form.getProcurementMode());
        contract.setPaymentTerms(form.getPaymentTerms());
        contract.setSupplierName(form.getSupplierName());
        entityManager.merge(contract);
        break;
      }
      default:
        throw new UnsupportedOperationException("还不支持");
    }

    processService.updateIncomeDetails(form.getIncomeDetails());

    if (form.getStatus() == ProcessStatus.to_be_audit) {
      processService.startAuditProgress(entity);
    }
  }

  @ResponseBody
  @PostMapping("/query")
  public Page<ProjectProcess> list(@RequestBody ProcessQueryForm form, Pageable pageable) {
    // 从 0 开始
    PageRequest pageRequest = new PageRequest(pageable.getPageNumber() - 1, pageable.getPageSize(),
            new Sort(Sort.Direction.DESC, "createAt"));

    ProjectProcess projectProcess = new ProjectProcess();
    BeanUtils.copyProperties(form, projectProcess);
    projectProcess.setStatus(form.getProcessStatus());

    ExampleMatcher exampleMatcher = ExampleMatcher.matching()
            .withMatcher("contractNo", ExampleMatcher.GenericPropertyMatcher::contains)
            .withMatcher("projectTitle", ExampleMatcher.GenericPropertyMatcher::contains)
            .withMatcher("applyPersonName", ExampleMatcher.GenericPropertyMatcher::contains);

    // 只展示自己创建的草稿
    // Admin admin = InterfaceUtil.getAdmin();
    // projectProcess.setApplyPersonId(admin.getId());

    return repository.findAll(Example.of(projectProcess, exampleMatcher), pageRequest);
  }

  // 审核
  @Data
  static class AuditForm {

    // 流程ID
    private Integer processId;

    // 审核意见
    @NotNull
    private String auditOpinion;

    // 审核状态
    private ProcessStatus processStatus;

  }

  @ResponseBody
  @PostMapping("/audit")
  public void audit(@RequestBody @Valid AuditForm form) {
    ProjectProcess process = repository.findOne(form.processId);
    ActApproveTypeEnum approveType = form.processStatus == ProcessStatus.audit_passed ? ActApproveTypeEnum.APPROVAL_PASSED :
                                     ActApproveTypeEnum.APPROVAL_UN_PASS;
    switch (process.getProcessType()) {
      case procurement_contract:
        projectInstanceService.completeTaskByProjectId(process.getId(), ActProjectTypeEnum.BUSINESS_PURCHASE, approveType, form.auditOpinion);
        break;
      case sale_contract:
        projectInstanceService.completeTaskByProjectId(process.getId(), ActProjectTypeEnum.SALE_CONTRACT, approveType, form.auditOpinion);
        break;
    }
  }

  @ResponseBody
  @PostMapping("/revoke/{id}")
  public void revoke(@PathVariable("id") int id) {
    // TODO 发起申请的人，在第一个人还没审批的情况下可以撤回
    processService.updateProcessStatus(id, ProcessStatus.draft);
  }

  @ResponseBody
  @DeleteMapping("/{id}")
  public void delete(@PathVariable("id") int id) {
    Admin admin = InterfaceUtil.getAdmin();
    Integer applyUserId = admin.getId();
    int update = jdbcTemplate.update("delete from project_process where id =? and apply_person_id=?", id, applyUserId);
    if (update != 1) {
      throw new RuntimeException("删除的流程不存在或者不属于自己");
    }
  }

  /**
   * 查看审核流程
   */
  @ResponseBody
  @GetMapping("/audits/{id}")
  public List<Map<String, String>> showAudits(@PathVariable("id") int id) {
//    ArrayList<Map<String, String>> ret = new ArrayList<>();
//    ret.add(create("2021-01-03 12:07:45", "提交审核"));
//    ret.add(create("2021-01-28 12:15:05", "工程运维管理部孙果审核已通过"));
//    ret.add(create("2021-02-03 22:01:12", "系统产品集成部尹浩审核已通过"));
//    ret.add(create("2021-03-03 22:05:12", "财务部主管何丹审核已通过"));

    ProjectProcess process = processService.getById(id);
    ProcessType processType = process.getProcessType();
    List<ProjectTaskRecord> projectTaskRecords = projectTaskRecordService.listV2(id,
            processType == ProcessType.sale_contract ? ActProjectTypeEnum.SALE_CONTRACT : ActProjectTypeEnum.BUSINESS_PURCHASE);
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return projectTaskRecords.stream()
            .map(item -> {
              Date createTime = item.getCreateTime();
              return create(format.format(createTime), item.getTaskName() + ": " + item.getTaskComment());
            }).collect(Collectors.toList());
  }

  private Map<String, String> create(String timestamp, String content) {
    Map<String, String> map = new HashMap<>();
    map.put("content", content);
    map.put("timestamp", timestamp);
    return map;
  }

}
