package de.avatic.taric.service;


import de.avatic.taric.error.ArgumentException;
import de.avatic.taric.model.*;
import de.avatic.taric.repository.ImportRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.*;

@Service
@Slf4j
public class TariffService {

    private final GeoService geoService;
    private final NomenclatureService nomenclatureService;
    private final ImportRepository importRepository;

    public TariffService(GeoService geoService, NomenclatureService nomenclatureService, ImportRepository importRepository) {
        this.geoService = geoService;
        this.nomenclatureService = nomenclatureService;
        this.importRepository = importRepository;
    }

    public Map<String, Map<String, List<AppliedMeasure>>> getAppliedMeasures(String hsCode, String countryCode) {

        Map<String, Map<String, List<AppliedMeasure>>> collectedMeasures = new HashMap<>();

        var geoGroups = geoService.getGeoGroupByCountryCode(countryCode);
        var optGeo = geoService.getGeo(countryCode);

        var nomenclature = nomenclatureService.getNomenclature(hsCode);
        var cascade = nomenclatureService.getNomenclatureCascade(hsCode);


        if (nomenclature.isEmpty() || !nomenclature.get().getIsLeaf()) throw new ArgumentException("hsCode");
        if (optGeo.isEmpty()) throw new ArgumentException("countryCode");

        var geo = optGeo.get();

        for (Nomenclature n : cascade) {
            var nomenclatureMeasures = new HashMap<String, List<AppliedMeasure>>();

            var measuresByOrigin = findAppliedMeasureByGeo(n, geo);

            if (!measuresByOrigin.isEmpty())
                nomenclatureMeasures.put(geo.getIso3166Code(), measuresByOrigin);

            for (var geoGroup : geoGroups) {
                var measuresByGeoGroup = findAppliedMeasureByGeoGroup(n, geo, geoGroup);
                if (!measuresByGeoGroup.isEmpty())
                    nomenclatureMeasures.put(geoGroup.getAbbr(), measuresByGeoGroup);
            }

            if (!nomenclatureMeasures.isEmpty())
                collectedMeasures.put(n.getHscode(), nomenclatureMeasures);
        }


        return collectedMeasures;

    }

    private Optional<Double> findTariff(Collection<AppliedMeasure> measures, Measure appl) {

        var code = appl.getMeasureCode();

        //TODO return all measures...

        List<AppliedMeasure> filteredMeasures = measures.stream()
                .filter(meas -> meas.getAmount() != null && meas.getAmount().trim().matches("\\d+\\.\\d+\\s*%"))
                .toList();

        AppliedMeasure customTariff = filteredMeasures.stream().filter(meas -> meas.getMeasure().getId().equals(appl.getId())).findAny().orElse(filteredMeasures.isEmpty() ? null : filteredMeasures.getFirst());

        if (!filteredMeasures.isEmpty())
            return Optional.of(customTariff).map(meas -> Double.parseDouble(meas.getAmount().trim().replace("%", "").trim()) / 100);
        return Optional.empty();
    }

    public List<AppliedMeasure> findAppliedMeasureByGeo(Nomenclature nomenclature, Geo geo) {

        var foundImport = importRepository.findByNomenclatureAndGeo(nomenclature.getId(), geo.getId());
        var applMeasures = new ArrayList<AppliedMeasure>();

        if (foundImport.isEmpty()) return Collections.emptyList();

        for (var imp : foundImport) {
            for (var applMeasure : imp.getAppliedMeasures()) {
                if (isCurrentlyValid(applMeasure) && applMeasure.getExclusions().stream().noneMatch(ex -> ex.getGeo().getId().equals(geo.getId()))) {
                    applMeasures.add(applMeasure);
                }
            }
        }

        return applMeasures;

    }


    public List<AppliedMeasure> findAppliedMeasureByGeoGroup(Nomenclature nomenclature, Geo geo, GeoGroup geoGroup) {
        var foundImport = importRepository.findByNomenclatureAndGeoGroup(nomenclature.getId(), geoGroup.getId());
        var applMeasures = new ArrayList<AppliedMeasure>();

        if (foundImport.isEmpty()) return Collections.emptyList();

        for (var imp : foundImport) {
            for (var applMeasure : imp.getAppliedMeasures()) {
                if (isCurrentlyValid(applMeasure) && applMeasure.getExclusions().stream().noneMatch(ex -> ex.getGeo().getId().equals(geo.getId()))) {
                    applMeasures.add(applMeasure);
                }
            }
        }

        return applMeasures;
    }

    private boolean isCurrentlyValid(AppliedMeasure applMeasure) {
        var start = applMeasure.getStartDate();
        var end = applMeasure.getEndDate();

        LocalDate now = LocalDate.now();

        return (start == null || start.isBefore(now)) && (end == null || end.isAfter(now));
    }

}
