package de.avatic.lcc.service.calculation;

import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
import de.avatic.lcc.dto.calculation.edit.SetDataDTO;
import de.avatic.lcc.model.packaging.PackagingDimension;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.model.properties.PackagingProperty;
import de.avatic.lcc.model.properties.PackagingPropertyMappingId;
import de.avatic.lcc.repositories.MaterialRepository;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.packaging.PackagingDimensionRepository;
import de.avatic.lcc.repositories.packaging.PackagingPropertiesRepository;
import de.avatic.lcc.repositories.packaging.PackagingRepository;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.CustomApiService;
import de.avatic.lcc.service.access.PremisesService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
public class ChangeMaterialService {

    private final PremiseRepository premiseRepository;
    private final PremisesService premisesService;
    private final CustomApiService customApiService;
    private final NodeRepository nodeRepository;
    private final UserNodeRepository userNodeRepository;
    private final PackagingRepository packagingRepository;
    private final PackagingDimensionRepository packagingDimensionRepository;
    private final PackagingPropertiesRepository packagingPropertiesRepository;
    private final MaterialRepository materialRepository;

    public ChangeMaterialService(PremiseRepository premiseRepository, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, MaterialRepository materialRepository) {
        this.premiseRepository = premiseRepository;
        this.premisesService = premisesService;
        this.customApiService = customApiService;
        this.nodeRepository = nodeRepository;
        this.userNodeRepository = userNodeRepository;
        this.packagingRepository = packagingRepository;
        this.packagingDimensionRepository = packagingDimensionRepository;
        this.packagingPropertiesRepository = packagingPropertiesRepository;
        this.materialRepository = materialRepository;
    }

    @Transactional
    public List<PremiseDetailDTO> setMaterial(SetDataDTO dto) {
        Integer userId = 1; /* TODO get user id */

        Integer materialId = dto.getMaterialId();
        List<Integer> premiseIds = dto.getPremiseId();

        if (materialId == null || premiseIds == null || premiseIds.isEmpty())
            throw new IllegalArgumentException("No supplier supplierNodeId or premises given");


        // get all premises first.
        List<Premise> allPremises = premiseRepository.getPremisesById(premiseIds);

        // find resulting duplicates, split into "keep" and "to be deleted".
        Map<Integer, Premise> uniqueMap = new HashMap<>();
        List<Premise> premisesToBeDeleted = new ArrayList<>();
        allPremises.forEach(p -> {
            if (null != uniqueMap.putIfAbsent(p.getSupplierNodeId(), p)) premisesToBeDeleted.add(p);
        });
        Collection<Premise> premisesToProcess = uniqueMap.values();

        // check if user owns all premises:
        if (allPremises.stream().anyMatch(p -> !p.getUserId().equals(userId)))
            throw new IllegalArgumentException("Not authorized to change material of premises owned by other users");

        // check for any other collisions, and mark as "to be deleted":
        premisesToBeDeleted.addAll(premisesToProcess.stream().map(p -> premiseRepository.getCollidingPremisesOnChange(userId, p.getId(), materialId, p.getSupplierNodeId())).flatMap(List::stream).toList());

        if(dto.isUpdateMasterData()) {

            var material = materialRepository.getById(materialId).orElseThrow(() -> new IllegalArgumentException("No material with id " + materialId));

            for (var premise : premisesToProcess) {
                var countryId = dto.isUserSupplierNode() ? userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow().getCountryId() : nodeRepository.getById(premise.getSupplierNodeId()).orElseThrow().getCountryId();
                var tariffRate = customApiService.getTariffRate(material.getHsCode(), countryId);
                premiseRepository.updateMaterial(Collections.singletonList(premise.getId()), material.getHsCode(), tariffRate);

                if (!dto.isUserSupplierNode()) {
                    var packaging = packagingRepository.getByMaterialIdAndSupplierId(dto.getMaterialId(), premise.getSupplierNodeId());
                    Optional<PackagingDimension> dimension = packaging.flatMap(p -> packagingDimensionRepository.getById(p.getHuId()));

                    if (dimension.isPresent()) {
                        boolean stackable =  packagingPropertiesRepository.getByPackagingIdAndType(packaging.get().getId(), PackagingPropertyMappingId.STACKABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
                        boolean mixable = packagingPropertiesRepository.getByPackagingIdAndType(packaging.get().getId(), PackagingPropertyMappingId.MIXABLE.name()).map(PackagingProperty::getValue).map(Boolean::valueOf).orElse(false);
                        premiseRepository.updatePackaging(Collections.singletonList(premise.getId()), dimension.get(), stackable, mixable);
                    }
                }
            }
        }

        // actually update materialId.
        premiseRepository.setMaterialId(premisesToProcess.stream().map(Premise::getId).toList(), materialId);


        //delete all conflicting premises:
        premisesService.delete(premisesToBeDeleted.stream().map(Premise::getId).toList());


        return premisesService.getPremises(premisesToProcess.stream().map(Premise::getId).toList());
    }

}
