package de.avatic.lcc.service.calculation.execution.steps;

import de.avatic.lcc.calculationmodel.ContainerCalculationResult;
import de.avatic.lcc.calculationmodel.CustomResult;
import de.avatic.lcc.calculationmodel.SectionInfo;
import de.avatic.lcc.model.premises.Premise;
import de.avatic.lcc.model.premises.route.Destination;
import de.avatic.lcc.model.properties.CountryPropertyMappingId;
import de.avatic.lcc.model.properties.CustomUnionType;
import de.avatic.lcc.model.properties.SystemPropertyMappingId;
import de.avatic.lcc.repositories.country.CountryPropertyRepository;
import de.avatic.lcc.repositories.premise.RouteNodeRepository;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.service.CustomApiService;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

@Service
public class CustomCostCalculationService {

    private final CustomApiService customApiService;
    private final CountryPropertyRepository countryPropertyRepository;
    private final RouteNodeRepository routeNodeRepository;
    private final PropertyRepository propertyRepository;
    private final ShippingFrequencyCalculationService shippingFrequencyCalculationService;

    public CustomCostCalculationService(CustomApiService customApiService, CountryPropertyRepository countryPropertyRepository, RouteNodeRepository routeNodeRepository, PropertyRepository propertyRepository, ShippingFrequencyCalculationService shippingFrequencyCalculationService) {
        this.customApiService = customApiService;
        this.countryPropertyRepository = countryPropertyRepository;
        this.routeNodeRepository = routeNodeRepository;
        this.propertyRepository = propertyRepository;
        this.shippingFrequencyCalculationService = shippingFrequencyCalculationService;
    }

    private BigDecimal getContainerShare(Premise premise, ContainerCalculationResult containerCalculationResult) {
        var weightExceeded = containerCalculationResult.isWeightExceeded();
        var mixable = premise.getHuMixable();

        if (mixable) {
            return BigDecimal.valueOf(weightExceeded ? containerCalculationResult.getHuUtilizationByWeight() : containerCalculationResult.getHuUtilizationByVolume());
        } else {
            return BigDecimal.ONE.divide(BigDecimal.valueOf(containerCalculationResult.getHuUnitCount()), 10, RoundingMode.HALF_UP);
        }
    }

    public CustomResult doD2dCalculation(Premise premise, Destination destination, List<SectionInfo> sections) {

        var destUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, destination.getCountryId()).orElseThrow();
        var sourceUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, premise.getCountryId()).orElseThrow();

        if (!CustomUnionType.EU.name().equals(destUnion.getCurrentValue()) || !CustomUnionType.NONE.name().equals(sourceUnion.getCurrentValue()))
            return CustomResult.EMPTY;

        double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(sections.getFirst().containerResult().getHuUnitCount()),2, RoundingMode.HALF_UP).doubleValue();

        return getCustomCalculationResult(premise, destination, getContainerShare(premise, sections.getFirst().containerResult()), huAnnualAmount, sections.getFirst().result().getAnnualCost(), sections.getFirst().result().getAnnualChanceCost(), sections.getFirst().result().getAnnualRiskCost());
    }

    public CustomResult doCalculation(Premise premise, Destination destination, List<SectionInfo> sections) {

        var destUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, destination.getCountryId()).orElseThrow();
        var sourceUnion = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, premise.getCountryId()).orElseThrow();

        if (CustomUnionType.EU.name().equals(destUnion.getCurrentValue()) && (sourceUnion.getCurrentValue() == null || CustomUnionType.NONE.name().equals(sourceUnion.getCurrentValue()))) {

            var relevantSections = getCustomRelevantRouteSections(sections);

            var transportationCost = relevantSections.stream().map(s -> s.result().getAnnualCost()).reduce(BigDecimal.ZERO, BigDecimal::add);
            var transportationChanceCost = relevantSections.stream().map(s -> s.result().getAnnualChanceCost()).reduce(BigDecimal.ZERO, BigDecimal::add);
            var transportationRiskCost = relevantSections.stream().map(s -> s.result().getAnnualRiskCost()).reduce(BigDecimal.ZERO, BigDecimal::add);


            double huAnnualAmount = BigDecimal.valueOf(destination.getAnnualAmount()).divide(BigDecimal.valueOf(relevantSections.getFirst().containerResult().getHuUnitCount()),2, RoundingMode.HALF_UP).doubleValue();

            return getCustomCalculationResult(premise, destination, getContainerShare(premise, relevantSections.getFirst().containerResult()), huAnnualAmount, transportationCost, transportationChanceCost, transportationRiskCost);
        }

        return CustomResult.EMPTY;
    }

    private CustomResult getCustomCalculationResult(Premise premise, Destination destination, BigDecimal containerShare, double huAnnualAmount, BigDecimal transportationCost, BigDecimal transportationChanceCost, BigDecimal transportationRiskCost) {
        var shippingFrequency = shippingFrequencyCalculationService.doCalculation(huAnnualAmount);
        var customFee = Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.CUSTOM_FEE).orElseThrow().getCurrentValue());

        var tariffRate = premise.getTariffRate() == null ? customApiService.getTariffRate(premise.getHsCode(), premise.getCountryId()) : premise.getTariffRate();

        var materialCost = premise.getMaterialCost().multiply(BigDecimal.valueOf(destination.getAnnualAmount()));
        var fcaFee = BigDecimal.ZERO;

        if (premise.getFcaEnabled()) {
            var fcaProperty = propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.FCA_FEE).orElseThrow();
            var fcaShare = Double.parseDouble(fcaProperty.getCurrentValue());
            fcaFee = BigDecimal.valueOf(fcaShare).multiply(materialCost);
        }

        var customValue = materialCost.add(fcaFee).add(transportationCost);
        var customDuties = customValue.multiply(tariffRate);
        var annualCustomFee = BigDecimal.valueOf(shippingFrequency).multiply(BigDecimal.valueOf(customFee)).multiply(containerShare);
        var annualCost = customDuties.add(annualCustomFee);

        var customRiskValue = materialCost.add(fcaFee).add(transportationRiskCost);
        var customRiskDuties = customRiskValue.multiply(tariffRate);
        var annualRiskCost = customRiskDuties.add(annualCustomFee);

        var customChanceValue = materialCost.add(fcaFee).add(transportationChanceCost);
        var customChanceDuties = customChanceValue.multiply(tariffRate);
        var annualChanceCost = customChanceDuties.add(annualCustomFee);

        return new CustomResult(customValue, customRiskValue, customChanceValue, customDuties, tariffRate, annualCost, annualRiskCost, annualChanceCost);
    }

    private List<SectionInfo> getCustomRelevantRouteSections(List<SectionInfo> sections) {

        List<SectionInfo> customSections = new ArrayList<>();

        for (SectionInfo section : sections) {
            if (!(CustomUnionType.EU == getCustomUnionByRouteNodeId(section.section().getFromRouteNodeId()) &&
                    CustomUnionType.EU == getCustomUnionByRouteNodeId(section.section().getToRouteNodeId()))) {
                customSections.add(section);
            }
        }

        return customSections;
    }

    private CustomUnionType getCustomUnionByRouteNodeId(Integer routeNodeId) {
        var node = routeNodeRepository.getById(routeNodeId).orElseThrow();
        return getCustomUnionByCountryId(node.getCountryId());
    }

    private CustomUnionType getCustomUnionByCountryId(Integer countryId) {
        var property = countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.UNION, countryId).orElseThrow();
        if (property.getCurrentValue() == null)
            return CustomUnionType.NONE;

        return CustomUnionType.valueOf(property.getCurrentValue().toUpperCase());
    }


}
