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

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.eshore.gringotts.web.domain.base.entity.SimpleEntity;
import com.eshore.gringotts.web.domain.base.repository.SimpleRepository;
import com.eshore.gringotts.web.domain.user.entity.User;
import com.eshore.gringotts.web.domain.user.service.UserService;
import java.util.Optional;
import javax.persistence.criteria.Predicate;
import javax.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.api.set.ImmutableSet;
import org.springframework.data.domain.Sort;

/**
 * @author lanyuanxiaoyao
 * @date 2024-11-21
 */
@Slf4j
public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implements SimpleService<ENTITY> {
    private final SimpleRepository<ENTITY, Long> repository;
    private final UserService userService;

    public SimpleServiceSupport(SimpleRepository<ENTITY, Long> repository, UserService userService) {
        this.repository = repository;
        this.userService = userService;
    }

    @Transactional(rollbackOn = Throwable.class)
    @Override
    public Long save(ENTITY entity) {
        if (ObjectUtil.isNotNull(entity.getId())) {
            Long id = entity.getId();
            ENTITY targetEntity = repository.findById(entity.getId()).orElseThrow(() -> new IdNotFoundException(id));
            BeanUtil.copyProperties(
                    entity,
                    targetEntity,
                    CopyOptions.create()
                            .setIgnoreProperties("id", "createdUser", "createdTime", "modifiedUser", "modifiedTime")
            );
            entity = targetEntity;
        }
        User user = userService.currentLoginUser();
        if (ObjectUtil.isNull(entity.getCreatedUser())) {
            entity.setCreatedUser(user);
        }
        entity.setModifiedUser(user);
        entity = repository.save(entity);
        return entity.getId();
    }

    @Override
    public ImmutableList<ENTITY> list() {
        return Lists.immutable.ofAll(repository.findAll(
                (root, query, builder) -> {
                    MutableList<Predicate> predicates = Lists.mutable.empty();
                    User user = userService.currentLoginUser();
                    if (User.isNotAdministrator(user)) {
                        predicates.add(builder.equal(root.get("createdUser"), user));
                    }
                    return builder.and(predicates.toArray(new Predicate[predicates.size()]));
                },
                Sort.by("createdTime").descending()
        ));
    }

    @Override
    public ImmutableList<ENTITY> list(ImmutableSet<Long> ids) {
        return Lists.immutable.ofAll(repository.findAll(
                (root, query, builder) -> {
                    MutableList<Predicate> predicates = Lists.mutable.of(
                            builder.in(root.get("id")).value(ids.select(ObjectUtil::isNotNull))
                    );
                    User user = userService.currentLoginUser();
                    if (User.isNotAdministrator(user)) {
                        predicates.add(builder.equal(root.get("createdUser"), user));
                    }
                    return builder.and(predicates.toArray(new Predicate[predicates.size()]));
                },
                Sort.by("createdTime").descending()
        ));
    }

    @Override
    public Optional<ENTITY> detailOptional(Long id) {
        if (ObjectUtil.isNull(id)) {
            return Optional.empty();
        }
        return repository.findOne(
                (root, query, builder) -> {
                    MutableList<Predicate> predicates = Lists.mutable.of(
                            builder.equal(root.get("id"), id)
                    );
                    User user = userService.currentLoginUser();
                    if (User.isNotAdministrator(user)) {
                        predicates.add(builder.equal(root.get("createdUser"), user));
                    }
                    return builder.and(predicates.toArray(new Predicate[predicates.size()]));
                }
        );
    }

    @Override
    public ENTITY detail(Long id) {
        return detailOrNull(id);
    }

    @Override
    public ENTITY detailOrThrow(Long id) {
        return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
    }

    @Override
    public ENTITY detailOrNull(Long id) {
        return detailOptional(id).orElse(null);
    }

    @Transactional(rollbackOn = Throwable.class)
    @Override
    public void remove(Long id) {
        if (ObjectUtil.isNotNull(id)) {
            repository.deleteById(id);
        }
    }

    public static final class IdNotFoundException extends RuntimeException {
        public IdNotFoundException(Long id) {
            super(StrUtil.format("ID为{}的资源不存在", id));
        }
    }
}
