package cn.lihongjie.coal.deviceCategory.service;

import cn.lihongjie.coal.base.dto.CommonQuery;
import cn.lihongjie.coal.base.dto.IdRequest;
import cn.lihongjie.coal.base.service.BaseService;
import cn.lihongjie.coal.common.TreeUtils;
import cn.lihongjie.coal.dbFunctions.DbFunctionService;
import cn.lihongjie.coal.deviceCategory.dto.CreateDeviceCategoryDto;
import cn.lihongjie.coal.deviceCategory.dto.DeviceCategoryDto;
import cn.lihongjie.coal.deviceCategory.dto.DeviceCategoryTreeDto;
import cn.lihongjie.coal.deviceCategory.dto.UpdateDeviceCategoryDto;
import cn.lihongjie.coal.deviceCategory.entity.DeviceCategoryEntity;
import cn.lihongjie.coal.deviceCategory.mapper.DeviceCategoryMapper;
import cn.lihongjie.coal.deviceCategory.repository.DeviceCategoryRepository;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Service
@Slf4j
@Transactional
public class DeviceCategoryService
        extends BaseService<DeviceCategoryEntity, DeviceCategoryRepository> {
    @Autowired private DeviceCategoryRepository repository;

    @Autowired private DeviceCategoryMapper mapper;

    @Autowired private ConversionService conversionService;

    @Autowired private DbFunctionService dbFunctionService;

    public DeviceCategoryDto create(CreateDeviceCategoryDto request) {
        DeviceCategoryEntity entity = mapper.toEntity(request);

        this.repository.save(entity);
        return getById(entity.getId());
    }

    public DeviceCategoryDto update(UpdateDeviceCategoryDto request) {
        DeviceCategoryEntity entity = this.repository.get(request.getId());
        this.mapper.updateEntity(entity, request);

        this.repository.save(entity);

        return getById(entity.getId());
    }

    public void delete(IdRequest request) {
        this.repository.deleteAllById(request.getIds());
    }

    public DeviceCategoryDto getById(String id) {
        DeviceCategoryEntity entity = repository.get(id);

        return mapper.toDto(entity);
    }

    public Page<DeviceCategoryDto> list(CommonQuery query) {
        Page<DeviceCategoryEntity> page =
                repository.findAll(
                        query.specification(conversionService),
                        PageRequest.of(
                                query.getPageNo(),
                                query.getPageSize(),
                                Sort.by(query.getOrders())));

        return page.map(this.mapper::toDto);
    }

    public List<DeviceCategoryTreeDto> getRoots(CommonQuery request) {

        if (CollectionUtils.isEmpty(request.getItems())) {
            List<DeviceCategoryEntity> roots =
                    this.repository.findAll(
                            (root, query, criteriaBuilder) ->
                                    criteriaBuilder.isNull(root.get("parent")));
            return roots.stream().map(de -> this.mapper.toTreeDto(de)).collect(Collectors.toList());
        } else {
            Page<DeviceCategoryEntity> page =
                    repository.findAll(
                            request.specification(conversionService),
                            PageRequest.of(
                                    request.getPageNo(),
                                    request.getPageSize(),
                                    Sort.by(request.getOrders())));

            List<String> selfAndParentIds =
                    this.dbFunctionService.selfAndParentIds(
                            dbFunctionService.entityToTableName(DeviceCategoryEntity.class),
                            page.stream().map(x -> x.getId()).collect(Collectors.toList()),
                            true);

            List<DeviceCategoryTreeDto> selfAndParent =
                    this.findAllByIds(selfAndParentIds).stream()
                            .map(x -> (this.mapper.toTreeDtoExcludeChildren(x)))
                            .collect(Collectors.toList());

            return StreamSupport.stream(
                            TreeUtils.buildTreeFromList(
                                            selfAndParent,
                                            DeviceCategoryTreeDto::getId,
                                            x -> x.getParent(),
                                            (x, y) -> {
                                                if (x.getChildren() == null) {
                                                    x.setChildren(new ArrayList<>());
                                                }
                                                x.getChildren().add(y);
                                                return null;
                                            })
                                    .spliterator(),
                            false)
                    .collect(Collectors.toList());
        }
    }

    public List<DeviceCategoryTreeDto> getTreeByIds(IdRequest request) {
        if (request.getIds().isEmpty()) {
            return new ArrayList<>();
        }
        var roots =
                this.repository.findAll(
                        (root, query, criteriaBuilder) -> root.get("id").in(request.getIds()));
        return roots.stream().map(de -> this.mapper.toTreeDto(de)).collect(Collectors.toList());
    }
}
