package com.izouma.jmrh.service;

import com.izouma.jmrh.domain.DockingRecord;
import com.izouma.jmrh.domain.OrgInfo;
import com.izouma.jmrh.domain.ResourceSupplyAndDemand;
import com.izouma.jmrh.dto.PageQuery;
import com.izouma.jmrh.dto.R;
import com.izouma.jmrh.dto.ResSnDAndPropertyDTO;
import com.izouma.jmrh.dto.SndDTO;
import com.izouma.jmrh.enums.AuditStatus;
import com.izouma.jmrh.enums.DockingRole;
import com.izouma.jmrh.enums.DockingStatus;
import com.izouma.jmrh.enums.ResSnDType;
import com.izouma.jmrh.exception.BusinessException;
import com.izouma.jmrh.repo.DockingRecordRepo;
import com.izouma.jmrh.repo.OrgInfoRepo;
import com.izouma.jmrh.repo.ResSnDPropertyRepo;
import com.izouma.jmrh.repo.ResourceSupplyAndDemandRepo;
import com.izouma.jmrh.utils.ObjUtils;
import com.izouma.jmrh.utils.SecurityUtils;
import com.izouma.jmrh.web.BaseController;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

@Service
@AllArgsConstructor
public class ResourceSupplyAndDemandService {

    private ResourceSupplyAndDemandRepo resourceSupplyAndDemandRepo;
    private DockingRecordRepo           dockingRecordRepo;
    private OrgInfoRepo                 orgInfoRepo;
    private ResSnDPropertyRepo          resSnDPropertyRepo;

    public R save(ResSnDAndPropertyDTO record) {
        if (record.getResSnD() == null || record.getPropertyList() == null) {
            throw new BusinessException("信息不完善");
        }
        if (record.getResSnD().getId() != null) {
            ResourceSupplyAndDemand orig = resourceSupplyAndDemandRepo.findById(record.getResSnD().getId())
                    .orElseThrow(new BusinessException("无记录"));
            ObjUtils.merge(orig, record.getResSnD());
            record.setResSnD(resourceSupplyAndDemandRepo.save(orig));
        } else {
            record.getResSnD().setStatus(AuditStatus.PENDING);
            record.getResSnD().setPublishersId(SecurityUtils.getAuthenticatedUser().getId());

            try {
                SecurityUtils.getAuthenticatedUser().getOrgInfo().getId();
            } catch (Exception e) {
                e.printStackTrace();
                throw new BusinessException("请先完成企业认证");
            }

            record.getResSnD().setOrgId(SecurityUtils.getAuthenticatedUser().getOrgInfo().getId());
            record.setResSnD(resourceSupplyAndDemandRepo.save(record.getResSnD()));
        }
        resSnDPropertyRepo.deleteByResSnDId(record.getResSnD().getId());
        record.getPropertyList().forEach(item -> {
            item.setId(null);
            item.setResSnDId(record.getResSnD().getId());
            resSnDPropertyRepo.save(item);
        });
        return R.success(record);
    }

    public SndDTO get(Long id, Long userId) {
        ResourceSupplyAndDemand snd = resourceSupplyAndDemandRepo.findById(id)
                .orElseThrow(new BusinessException("无记录"));
        OrgInfo orgInfo = orgInfoRepo.findById(snd.getOrgId())
                .orElseThrow(new BusinessException("无记录"));
        SndDTO dto = toDTO(snd, userId);
        dto.setOrgInfo(orgInfo);
        return dto;
    }

    public Page<SndDTO> allDTO(Long userId, PageQuery pageQuery) {
        Map<String, String> property = (Map<String, String>) pageQuery.getQuery().get("property");
        Specification<ResourceSupplyAndDemand> specification =
                BaseController.toSpecification(pageQuery, ResourceSupplyAndDemand.class);
        if (property != null && !property.isEmpty()) {
            specification = specification
                    .and((Specification<ResourceSupplyAndDemand>) (root, criteriaQuery, criteriaBuilder) -> {
                        List<Predicate> or = new ArrayList<>();
                        property.forEach((name, value) -> {
                            or.add(criteriaBuilder.and(
                                    criteriaBuilder.equal(root.join("resSnDPropertyList").get("name"), name),
                                    criteriaBuilder.equal(root.join("resSnDPropertyList").get("value"), value)
                            ));
                        });
                        return criteriaBuilder.or(or.toArray(new Predicate[0]));
                    });
        }
        Page<ResourceSupplyAndDemand> page = resourceSupplyAndDemandRepo
                .findAll(specification, BaseController.toPageRequest(pageQuery));
        return toDTOPage(page, userId);
    }

    public Page<SndDTO> my(Long userId, List<ResSnDType> types, Pageable pageable) {
        Page<ResourceSupplyAndDemand> page = resourceSupplyAndDemandRepo
                .findAll((Specification<ResourceSupplyAndDemand>) (root, criteriaQuery, criteriaBuilder) -> {
                    List<Predicate> predicates = new ArrayList<>();
                    predicates.add(criteriaBuilder.equal(root.get("publishersId"), userId));
                    if (types != null && !types.isEmpty()) {
                        predicates.add(root.get("type").in(types));
                    }
                    return criteriaQuery.where(predicates.toArray(new Predicate[0]))
                            .orderBy(criteriaBuilder.desc(root.get("createdAt")))
                            .getRestriction();
                }, pageable);
        return toDTOPage(page, userId);
    }

    public Page<SndDTO> myCollect(Long userId, List<ResSnDType> types, Pageable pageable) {
        Page<ResourceSupplyAndDemand> page;
        if (types.isEmpty()) {
            page = resourceSupplyAndDemandRepo.myCollect(userId, pageable);
        } else {
            page = resourceSupplyAndDemandRepo.myCollect(userId, types, pageable);
        }
        return toDTOPage(page, userId);
    }

    public Page<SndDTO> myDocking(Long userId, DockingRole role, List<ResSnDType> types, Pageable pageable) {
        Page<SndDTO> page;
        if (types != null && !types.isEmpty()) {
            page = resourceSupplyAndDemandRepo.myDockingSnd(userId, role, types, pageable);
        } else {
            page = resourceSupplyAndDemandRepo.myDockingSnd(userId, role, pageable);
        }
        addOrgInfo(page);
        return page;
    }

    private Page<SndDTO> toDTOPage(Page<ResourceSupplyAndDemand> page, Long userId) {
        Page<SndDTO> dtoPage = page.map(snd -> toDTO(snd, userId));
        addOrgInfo(dtoPage);
        return dtoPage;
    }

    private SndDTO toDTO(ResourceSupplyAndDemand snd, Long userId) {
        SndDTO dto = new SndDTO();
        BeanUtils.copyProperties(snd, dto);
        if (userId != null && !snd.getPublishersId().equals(userId)) {
            dto.setDockingStatus(Optional.ofNullable(dockingRecordRepo
                    .findFirstByUserIdAndSndIdAndRoleOrderByIdDesc(userId, snd.getId(), DockingRole.DOCKING))
                    .map(DockingRecord::getStatus).orElse(null));
        }
        return dto;
    }

    private void addOrgInfo(Page<SndDTO> page) {
        Set<Long> orgIds = page.stream().map(SndDTO::getOrgId).filter(Objects::nonNull).collect(Collectors.toSet());
        List<OrgInfo> orgInfos = new ArrayList<>();
        if (!orgIds.isEmpty()) {
            orgInfos = orgInfoRepo.findAllById(orgIds);
        }
        for (SndDTO dto : page) {
            dto.setOrgInfo(orgInfos
                    .stream()
                    .filter(orgInfo -> orgInfo.getId().equals(dto.getOrgId()))
                    .findFirst()
                    .orElse(null));
        }
    }
}
