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.LogicDeleteService;
import com.eshore.gringotts.web.domain.entity.Authentication;
import com.eshore.gringotts.web.domain.entity.Authentication_;
import com.eshore.gringotts.web.domain.entity.CheckOrder;
import com.eshore.gringotts.web.domain.entity.CheckOrder_;
import com.eshore.gringotts.web.domain.entity.User;
import com.eshore.gringotts.web.domain.repository.AuthenticationRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
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-12-02
 */
@Slf4j
@Service("com.eshore.gringotts.web.domain.service.AuthenticationService")
public class AuthenticationService extends LogicDeleteService<Authentication> implements CheckingService {
    private final AuthenticationRepository authenticationRepository;
    private final UserService userService;
    private final CheckOrderService checkOrderService;
    private final ObjectMapper mapper;

    public AuthenticationService(AuthenticationRepository repository, UserService userService, EntityManager manager, CheckOrderService checkOrderService, Jackson2ObjectMapperBuilder builder) {
        super(repository, userService, manager);
        this.authenticationRepository = repository;
        this.userService = userService;
        this.checkOrderService = checkOrderService;
        this.mapper = builder.build();
    }

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

    @Override
    public Long save(Authentication entity) {
        if (ObjectUtil.isNull(entity.getId()) && authenticationRepository.findOne(
                (root, query, criteriaBuilder) -> {
                    // TODO 同一个资源的授权时间是否重合
                    // 查询是否存在createdUser为当前登陆用户并且绑定的数据资源是同一个的授权申请
                    return criteriaBuilder.and(
                            criteriaBuilder.equal(root.get("createdUser"), userService.currentLoginUser()),
                            criteriaBuilder.equal(root.get("target"), entity.getTarget())
                    );
                }
        ).isPresent()) {
            throw new AuthenticationDuplicatedException();
        }
        return super.save(entity);
    }

    @Transactional(rollbackOn = Throwable.class)
    public void submit(Long id) throws JsonProcessingException {
        Authentication authentication = detailOrThrow(id);
        authentication.setState(CheckingNeededEntity.State.OWNER_CHECKING);
        Long orderId = checkOrderService.save(new CheckOrder(
                "authentication_owner_check",
                StrUtil.format("数据资源「{}」的授权申请", authentication.getTarget().getName()),
                CheckOrder.Type.AUTHENTICATION,
                mapper.writeValueAsString(Maps.immutable.of("authenticationId", authentication.getId())),
                "com.eshore.gringotts.web.domain.service.AuthenticationService",
                authentication.getCreatedUser()
        ));
        CheckOrder order = checkOrderService.detailOrThrow(orderId);
        authentication.setOrder(order);
        save(authentication);
    }

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

        CheckOrder order = authentication.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) {
        Long id = (Long) parameters.get("authenticationId");
        Authentication authentication = detailOrThrow(id);
        if (StrUtil.equals(order.getKeyword(), "authentication_owner_check")) {
            switch (operation) {
                case APPLY:
                    authentication.setState(Authentication.State.CHECKING);
                    order.setKeyword("authentication_checker_check");
                    order.setTarget(CheckOrder.Target.ROLE);
                    order.setTargetRole(User.Role.CHECKER);
                    break;
                case REJECT:
                    authentication.setState(Authentication.State.DRAFT);
                    order.setState(CheckOrder.State.OVER);
                    break;
            }
        } else if (StrUtil.equals(order.getKeyword(), "authentication_checker_check")) {
            switch (operation) {
                case APPLY:
                    authentication.setState(Authentication.State.NORMAL);
                    order.setState(CheckOrder.State.OVER);
                    break;
                case REJECT:
                    authentication.setState(Authentication.State.DRAFT);
                    order.setState(CheckOrder.State.OVER);
                    break;
            }
        }
        save(authentication);
        checkOrderService.save(order);
    }

    public static final class AuthenticationDuplicatedException extends RuntimeException {
        public AuthenticationDuplicatedException() {
            super("数据资源已绑定该账号的授权申请，无法再次申请");
        }
    }
}
