package de.avatic.lcc.service.api;

import de.avatic.lcc.dto.custom.CustomDTO;
import de.avatic.lcc.dto.custom.CustomMeasureDTO;
import de.avatic.lcc.dto.generic.PropertyDTO;
import de.avatic.lcc.model.custom.MeasureType;
import de.avatic.lcc.model.db.properties.SystemPropertyMappingId;
import de.avatic.lcc.repositories.country.CountryRepository;
import de.avatic.lcc.repositories.properties.PropertyRepository;
import de.avatic.lcc.util.exception.base.InternalErrorException;
import eu.europa.ec.taxation.taric.client.GoodsMeasuresForWsResponse;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;

@Service
public class EUTaxationApiWrapperService {

    private final CountryRepository countryRepository;
    private final EUTaxationApiService eUTaxationApiService;
    private final PropertyRepository propertyRepository;

    public EUTaxationApiWrapperService(CountryRepository countryRepository, EUTaxationApiService eUTaxationApiService, PropertyRepository propertyRepository) {
        this.countryRepository = countryRepository;
        this.eUTaxationApiService = eUTaxationApiService;
        this.propertyRepository = propertyRepository;
    }

    public List<CustomDTO> getTariffRates(String hsCode, List<Integer> countryId) {
        var futures = countryId.stream().map(country -> getTariffRate(hsCode, country)).toList();

        return futures.stream().map(CompletableFuture::join).toList();
    }


    public CustomDTO getTariffRateImmediate(String hsCode, Integer countryId) {
        var country = countryRepository.getById(countryId);
        String iso = country.orElseThrow().getIsoCode().name();

        List<CustomMeasureDTO> customMeasures = new ArrayList<>();

        try {
            var measForWsResponse = eUTaxationApiService.getGoodsMeasures(hsCode, iso.toUpperCase(), "I");

            GoodsMeasuresForWsResponse.Measures.Measure selectedMeasure = null;
            Double selectedDuty = null;
            int rank = Integer.MAX_VALUE;

            var measures = filterToNewestMeasuresPerType(measForWsResponse.getReturn().getResult().getMeasures().getMeasure());


            for (var measure : measures) {
                var measureType = MeasureType.fromMeasureCode(measure.getMeasureType().getMeasureType());
                boolean maybeRelevant = measureType.map(MeasureType::containsRelevantDuty).orElse(false);

                if (maybeRelevant) {
                    var duty = extractDuty(measure);

                    if (rank > measureType.get().ordinal() && duty.isPresent()) {
                        rank = measureType.get().ordinal();
                        selectedDuty = duty.get();
                        selectedMeasure = measure;
                    }
                }

                customMeasures.add(new CustomMeasureDTO(
                        measure.getMeasureType().getMeasureType(),
                        measure.getMeasureType().getDescription(),
                        measure.getRegulationId(),
                        measure.getDutyRate()));
            }

            if (selectedDuty != null) {
                return new CustomDTO(false, selectedDuty, customMeasures, countryId);
            }


        } catch (Exception e) {
            // just continue
        }

        return propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.TARIFF_RATE).map(PropertyDTO::getCurrentValue).map(Double::valueOf).map(d -> new CustomDTO(d, customMeasures, countryId)).orElseThrow(() -> new InternalErrorException("Unable to load default custom rate. Please contact support."));

    }

    @Async
    public CompletableFuture<CustomDTO> getTariffRate(String hsCode, Integer countryId) {
        var country = countryRepository.getById(countryId);
        String iso = country.orElseThrow().getIsoCode().name();

        List<CustomMeasureDTO> customMeasures = new ArrayList<>();

        try {
            var measForWsResponse = eUTaxationApiService.getGoodsMeasures(hsCode, iso.toUpperCase(), "I");

            GoodsMeasuresForWsResponse.Measures.Measure selectedMeasure = null;
            Double selectedDuty = null;
            int rank = Integer.MAX_VALUE;

            var measures = filterToNewestMeasuresPerType(measForWsResponse.getReturn().getResult().getMeasures().getMeasure());


            for (var measure : measures) {
                var measureType = MeasureType.fromMeasureCode(measure.getMeasureType().getMeasureType());
                boolean maybeRelevant = measureType.map(MeasureType::containsRelevantDuty).orElse(false);

                if (maybeRelevant) {
                    var duty = extractDuty(measure);

                    if (rank > measureType.get().ordinal() && duty.isPresent()) {
                        rank = measureType.get().ordinal();
                        selectedDuty = duty.get();
                        selectedMeasure = measure;
                    }
                }

                customMeasures.add(new CustomMeasureDTO(
                        measure.getMeasureType().getMeasureType(),
                        measure.getMeasureType().getDescription(),
                        measure.getRegulationId(),
                        measure.getDutyRate()));
            }

            if (selectedDuty != null) {
                return CompletableFuture.completedFuture(new CustomDTO(false, selectedDuty, customMeasures, countryId));
            }


        } catch (Exception e) {
            // just continue
        }

        return CompletableFuture.completedFuture(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.TARIFF_RATE).map(PropertyDTO::getCurrentValue).map(Double::valueOf).map(d -> new CustomDTO(d, customMeasures, countryId)).orElseThrow(() -> new InternalErrorException("Unable to load default custom rate. Please contact support.")));

    }

    public List<GoodsMeasuresForWsResponse.Measures.Measure> filterToNewestMeasuresPerType(List<GoodsMeasuresForWsResponse.Measures.Measure> measures) {
        Map<String, GoodsMeasuresForWsResponse.Measures.Measure> newestByType = new HashMap<>();

        for (GoodsMeasuresForWsResponse.Measures.Measure measure : measures) {
            String measureTypeKey = measure.getMeasureType().getMeasureType();

            GoodsMeasuresForWsResponse.Measures.Measure existing = newestByType.get(measureTypeKey);

            if (existing == null ||
                    measure.getValidityStartDate().compare(existing.getValidityStartDate()) > 0) {
                newestByType.put(measureTypeKey, measure);
            }
        }

        return new ArrayList<>(newestByType.values());
    }

    private Optional<Double> extractDuty(GoodsMeasuresForWsResponse.Measures.Measure measure) {

        var dutyRate = measure.getDutyRate();

        if(dutyRate == null) return Optional.empty();

        if (dutyRate.trim().matches("\\d+\\.\\d+\\s*%")) {
            return Optional.of(Double.parseDouble(dutyRate.trim().replace("%", "").trim()) / 100);
        }

        return Optional.empty();

    }


}
