package com.eshore.gringotts.web.domain.service;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.eshore.gringotts.web.domain.base.entity.CheckingNeededEntity;
import com.eshore.gringotts.web.domain.base.service.CheckingService;
import com.eshore.gringotts.web.domain.base.service.SimpleServiceSupport;
import com.eshore.gringotts.web.domain.entity.CheckOrder;
import com.eshore.gringotts.web.domain.entity.CheckOrder_;
import com.eshore.gringotts.web.domain.entity.Confirmation;
import com.eshore.gringotts.web.domain.entity.Confirmation_;
import com.eshore.gringotts.web.domain.entity.User;
import com.eshore.gringotts.web.domain.repository.ConfirmationRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.factory.Maps;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.map.ImmutableMap;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.stereotype.Service;

/**
 * @author lanyuanxiaoyao
 * @date 2024-11-26
 */
@Slf4j
@Service("com.eshore.gringotts.web.domain.service.ConfirmationService")
public class ConfirmationService extends SimpleServiceSupport<Confirmation> implements CheckingService {
    private final ConfirmationRepository confirmationRepository;
    private final CheckOrderService checkOrderService;
    private final ObjectMapper mapper;

    public ConfirmationService(ConfirmationRepository confirmationRepository, UserService userService, CheckOrderService checkOrderService, Jackson2ObjectMapperBuilder builder) {
        super(confirmationRepository, userService);
        this.confirmationRepository = confirmationRepository;
        this.checkOrderService = checkOrderService;
        this.mapper = builder.build();
    }

    @Override
    protected ImmutableList<Predicate> listPredicate(Root<Confirmation> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        User loginUser = userService.currentLoginUser();
        if (User.isAdministrator(loginUser)) {
            return Lists.immutable.empty();
        }
        Join<Confirmation, CheckOrder> orderJoin = root.join(Confirmation_.order, JoinType.LEFT);
        return Lists.immutable.of(builder.or(
                builder.equal(root.get(Confirmation_.createdUser), loginUser),
                builder.and(
                        builder.isNotNull(orderJoin),
                        builder.or(
                                builder.and(
                                        builder.equal(orderJoin.get(CheckOrder_.target), CheckOrder.Target.ROLE),
                                        builder.equal(orderJoin.get(CheckOrder_.targetRole), loginUser.getRole())
                                ),
                                builder.and(
                                        builder.equal(orderJoin.get(CheckOrder_.target), CheckOrder.Target.USER),
                                        builder.equal(orderJoin.get(CheckOrder_.targetUser), loginUser)
                                )
                        )
                )
        ));
    }

    @Override
    public Long save(Confirmation entity) {
        if (ObjectUtil.isNull(entity.getId()) && confirmationRepository.existsByTarget_Id(entity.getTarget().getId())) {
            throw new ConfirmationDuplicatedException();
        }
        return super.save(entity);
    }

    @Transactional(rollbackOn = Throwable.class)
    public void submit(Long id) throws JsonProcessingException {
        Confirmation confirmation = detailOrThrow(id);
        confirmation.setState(Confirmation.State.CHECKING);
        Long orderId = checkOrderService.save(new CheckOrder(
                "confirmation_check",
                StrUtil.format("数据资源「{}」的确权申请", confirmation.getTarget().getName()),
                CheckOrder.Type.CONFIRMATION,
                mapper.writeValueAsString(Maps.immutable.of("confirmationId", confirmation.getId())),
                "com.eshore.gringotts.web.domain.service.ConfirmationService",
                User.Role.CHECKER
        ));
        CheckOrder order = checkOrderService.detailOrThrow(orderId);
        confirmation.setOrder(order);
        save(confirmation);
    }

    @Transactional(rollbackOn = Throwable.class)
    public void retract(Long id) {
        Confirmation confirmation = detailOrThrow(id);
        confirmation.setState(CheckingNeededEntity.State.DRAFT);
        save(confirmation);

        CheckOrder order = confirmation.getOrder();
        order.setState(CheckOrder.State.RETRACT);
        checkOrderService.save(order);
    }

    @Override
    @Transactional(rollbackOn = Throwable.class)
    public void onChecked(CheckOrder order, CheckOrder.Operation operation, ImmutableMap<String, Object> parameters) {
        if (StrUtil.equals(order.getKeyword(), "confirmation_check")) {
            Long id = (Long) parameters.get("confirmationId");
            Confirmation confirmation = detailOrThrow(id);
            switch (operation) {
                case APPLY:
                    confirmation.setState(Confirmation.State.NORMAL);
                    order.setState(CheckOrder.State.OVER);
                    break;
                case REJECT:
                    confirmation.setState(Confirmation.State.DRAFT);
                    order.setState(CheckOrder.State.OVER);
                    break;
            }
            save(confirmation);
            checkOrderService.save(order);
        }
    }

    public static final class ConfirmationDuplicatedException extends RuntimeException {
        public ConfirmationDuplicatedException() {
            super("数据资源已绑定确权申请，无法再次申请");
        }
    }
}
