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.db.nodes.Node;
import de.avatic.lcc.model.db.packaging.PackagingDimension;
import de.avatic.lcc.model.db.premises.Premise;
import de.avatic.lcc.model.db.premises.route.Destination;
import de.avatic.lcc.model.db.properties.PackagingProperty;
import de.avatic.lcc.model.db.properties.PackagingPropertyMappingId;
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.*;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.api.CustomApiService;
import de.avatic.lcc.service.access.DestinationService;
import de.avatic.lcc.service.access.PremisesService;
import de.avatic.lcc.service.api.EUTaxationApiWrapperService;
import de.avatic.lcc.service.users.AuthorizationService;
import de.avatic.lcc.util.exception.badrequest.InvalidArgumentException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
public class ChangeSupplierService {

    private final PremiseRepository premiseRepository;
    private final DestinationService destinationService;
    private final RoutingService routingService;
    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 DestinationRepository destinationRepository;
    private final RouteRepository routeRepository;
    private final RouteSectionRepository routeSectionRepository;
    private final RouteNodeRepository routeNodeRepository;
    private final AuthorizationService authorizationService;
    private final EUTaxationApiWrapperService eUTaxationApiWrapperService;

    public ChangeSupplierService(PremiseRepository premiseRepository, DestinationService destinationService, RoutingService routingService, PremisesService premisesService, CustomApiService customApiService, NodeRepository nodeRepository, UserNodeRepository userNodeRepository, PackagingRepository packagingRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, DestinationRepository destinationRepository, RouteRepository routeRepository, RouteSectionRepository routeSectionRepository, RouteNodeRepository routeNodeRepository, AuthorizationService authorizationService, EUTaxationApiWrapperService eUTaxationApiWrapperService) {
        this.premiseRepository = premiseRepository;
        this.destinationService = destinationService;
        this.routingService = routingService;
        this.premisesService = premisesService;
        this.customApiService = customApiService;
        this.nodeRepository = nodeRepository;
        this.userNodeRepository = userNodeRepository;
        this.packagingRepository = packagingRepository;
        this.packagingDimensionRepository = packagingDimensionRepository;
        this.packagingPropertiesRepository = packagingPropertiesRepository;
        this.destinationRepository = destinationRepository;
        this.routeRepository = routeRepository;
        this.routeSectionRepository = routeSectionRepository;
        this.routeNodeRepository = routeNodeRepository;
        this.authorizationService = authorizationService;
        this.eUTaxationApiWrapperService = eUTaxationApiWrapperService;
    }


    @Transactional
    public List<PremiseDetailDTO> setSupplier(SetDataDTO dto) {

        var userId = authorizationService.getUserId();
        Integer supplierNodeId = dto.getSupplierNodeId();
        List<Integer> premiseIds = dto.getPremiseId();

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

        Node supplier = dto.isUserSupplierNode() ? userNodeRepository.getById(supplierNodeId).orElseThrow() : nodeRepository.getById(supplierNodeId).orElseThrow();

        // 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.getMaterialId(), 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 suppliers 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(), p.getMaterialId(), supplierNodeId)).flatMap(List::stream).toList());

        // delete all routes of all destinations
        destinationService.deleteAllDestinationsByPremiseId(premisesToProcess.stream().map(Premise::getId).toList(), true);




        //recalculate routes:
        for (Premise premise : premisesToProcess) {
            List<Destination> destination = destinationRepository.getByPremiseId(premise.getId());
            for (Destination d : destination) {
                Node destinationNode = nodeRepository.getById(d.getDestinationNodeId()).orElseThrow();
                destinationService.findRouteAndSave(d.getId(), destinationNode,  supplier, dto.isUserSupplierNode() );
            }
        }

        //update master data:
        if (dto.isUpdateMasterData()) {
            for (var premise : premisesToProcess) {
                var tariffRate = eUTaxationApiWrapperService.getTariffRateImmediate(premise.getHsCode(), supplier.getCountryId());
                premiseRepository.updateTariffRate(premise.getId(), tariffRate.getValue());

                if (!dto.isUserSupplierNode()) {
                    var packaging = packagingRepository.getByMaterialIdAndSupplierId(premise.getMaterialId(), supplierNodeId);
                    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 supplier supplierNodeId.
        premiseRepository.setSupplierId(premisesToProcess.stream().map(Premise::getId).toList(), supplier, dto.isUserSupplierNode());


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


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

    }



}
