package de.avatic.taric.service;

import de.avatic.taric.model.*;
import de.avatic.taric.repository.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.data.jdbc.core.mapping.AggregateReference;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class TariffService2 {

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TariffService2.class);

    private final ImportRepository importRepository;
    private final AppliedMeasureRepository appliedMeasureRepository;
    private final NomenclatureRepository nomenclatureRepository;
    private final GeoRepository geoRepository;
    private final GeoGroupRepository geoGroupRepository;
    private final GeoGroupMembershipRepository geoGroupMembershipRepository;
    private final MeasureRepository measureRepository;
    private final MeasureExclusionRepository measureExclusionRepository;

    public TariffService2(ImportRepository importRepository, AppliedMeasureRepository appliedMeasureRepository, NomenclatureRepository nomenclatureRepository, GeoRepository geoRepository, GeoGroupRepository geoGroupRepository, GeoGroupMembershipRepository geoGroupMembershipRepository, MeasureRepository measureRepository, MeasureExclusionRepository measureExclusionRepository) {
        this.importRepository = importRepository;
        this.appliedMeasureRepository = appliedMeasureRepository;
        this.nomenclatureRepository = nomenclatureRepository;
        this.geoRepository = geoRepository;
        this.geoGroupRepository = geoGroupRepository;
        this.geoGroupMembershipRepository = geoGroupMembershipRepository;
        this.measureRepository = measureRepository;
        this.measureExclusionRepository = measureExclusionRepository;
    }

    /**
     * Ermittelt den Zolltarif für einen HS-Code und ein Herkunftsland
     *
     * @param hsCode HS-Code (kann 6, 8 oder 10 Stellen haben)
     * @param countryCode ISO-2 Ländercode (z.B. "CN" für China)
     * @return Zolltarif in Prozent
     */
    @Transactional(readOnly = true)
    public TariffResult getTariffRate(String hsCode, String countryCode) {
        log.info("Getting tariff rate for HS Code: {} from country: {}", hsCode, countryCode);

        // Normalisiere HS-Code auf 10 Stellen
        String normalizedHsCode = normalizeHsCode(hsCode);

        // Finde alle relevanten Nomenclature-Codes (inkl. Parent-Codes durch Cascade-Prinzip)
        List<String> relevantCodes = findRelevantNomenclatureCodes(normalizedHsCode);
        log.debug("Found relevant codes: {}", relevantCodes);

        // Hole das Land
        Optional<Geo> countryOpt = geoRepository.findByIso3166Code(countryCode);
        if (countryOpt.isEmpty()) {
            log.warn("Country not found: {}", countryCode);
            return TariffResult.notFound("Country code not found: " + countryCode);
        }

        Geo country = countryOpt.get();

        // Finde alle Imports für die relevanten Codes
        List<Import> imports = findRelevantImports(relevantCodes, country);

        if (imports.isEmpty()) {
            log.info("No imports found for codes: {} and country: {}", relevantCodes, countryCode);
            // Versuche Erga Omnes (alle Länder)
            imports = findErgaOmnesImports(relevantCodes);
        }

        if (imports.isEmpty()) {
            return TariffResult.notFound("No tariff data found for HS code: " + hsCode);
        }

        // Finde die anwendbaren Maßnahmen
        BigDecimal tariffRate = calculateTariffRate(imports, country);

        return TariffResult.success(tariffRate, normalizedHsCode, countryCode);
    }

    private String normalizeHsCode(String hsCode) {
        // Entferne alle nicht-numerischen Zeichen
        String cleaned = hsCode.replaceAll("[^0-9]", "");

        // Fülle auf 10 Stellen mit Nullen auf
        while (cleaned.length() < 10) {
            cleaned += "0";
        }

        // Begrenze auf 10 Stellen
        if (cleaned.length() > 10) {
            cleaned = cleaned.substring(0, 10);
        }

        return cleaned;
    }

    private List<String> findRelevantNomenclatureCodes(String hsCode) {
        List<String> codes = new ArrayList<>();
        codes.add(hsCode);

        // Füge Parent-Codes hinzu (Cascade-Prinzip)
        // Beispiel: 8504101010 -> auch 85041010, 850410, 8504, 85
        String code = hsCode;
        while (code.length() > 2) {
            // Entferne die letzten 2 Nullen
            if (code.endsWith("00")) {
                code = code.substring(0, code.length() - 2);
                codes.add(code + "0".repeat(10 - code.length()));
            } else {
                break;
            }
        }

        return codes;
    }

    private List<Import> findRelevantImports(List<String> nomenclatureCodes, Geo country) {
        List<Import> imports = new ArrayList<>();

        for (String code : nomenclatureCodes) {
            // Suche direkte Länder-Zuordnungen
            Optional<Nomenclature> nomenclatureOpt = nomenclatureRepository.findByHscode(code);
            if (nomenclatureOpt.isPresent()) {
                Nomenclature nomenclature = nomenclatureOpt.get();

                // Suche Imports mit direkter Geo-Zuordnung
                imports.addAll(importRepository.findByNomenclatureIdAndGeoId(
                        nomenclature.getId(), country.getId()));

                // Suche auch nach Ländergruppen-Mitgliedschaften
                List<GeoGroupMembership> memberships =
                        geoGroupMembershipRepository.findByGeoId(country.getId());

                for (GeoGroupMembership membership : memberships) {
                    // Hole die geo_group_id aus der membership
                    Integer geoGroupId = geoGroupMembershipRepository
                            .findGeoGroupIdByMembershipId(membership.getId());
                    if (geoGroupId != null) {
                        imports.addAll(importRepository.findByNomenclatureIdAndGeoGroupId(
                                nomenclature.getId(), geoGroupId));
                    }
                }
            }
        }

        return imports;
    }

    private List<Import> findErgaOmnesImports(List<String> nomenclatureCodes) {
        List<Import> imports = new ArrayList<>();

        // Erga Omnes hat normalerweise den Code "1011"
        Optional<GeoGroup> ergaOmnes = geoGroupRepository.findByGeoGroupCode("1011");
        if (ergaOmnes.isEmpty()) {
            return imports;
        }

        for (String code : nomenclatureCodes) {
            Optional<Nomenclature> nomenclatureOpt = nomenclatureRepository.findByHscode(code);
            if (nomenclatureOpt.isPresent()) {
                imports.addAll(importRepository.findByNomenclatureIdAndGeoGroupId(
                        nomenclatureOpt.get().getId(), ergaOmnes.get().getId()));
            }
        }

        return imports;
    }

    private BigDecimal calculateTariffRate(List<Import> imports, Geo country) {
        BigDecimal lowestRate = null;
        LocalDate today = LocalDate.now();

        for (Import imp : imports) {
            // Nutze die korrekte Repository-Methode mit Import-Reference
            List<AppliedMeasure> measures = appliedMeasureRepository
                    .findByImport(AggregateReference.to(imp.getId()));

            for (AppliedMeasure appliedMeasure : measures) {
                // Prüfe ob Maßnahme gültig ist
                if (!isMeasureValid(appliedMeasure, today)) {
                    continue;
                }

                // Prüfe ob das Land ausgeschlossen ist
                if (isCountryExcluded(appliedMeasure, country)) {
                    continue;
                }

                // Hole die Maßnahme über die AggregateReference
                if (appliedMeasure.getMeasure() == null) {
                    continue;
                }

                Optional<Measure> measureOpt = measureRepository.findById(
                        appliedMeasure.getMeasure().getId());
                if (measureOpt.isEmpty()) {
                    continue;
                }

                Measure measure = measureOpt.get();

                // Wir interessieren uns hauptsächlich für Third Country Duty (103)
                // und Preferential Tariff (142, 143)
                if (!"103".equals(measure.getMeasureCode()) &&
                        !"142".equals(measure.getMeasureCode()) &&
                        !"143".equals(measure.getMeasureCode())) {
                    continue;
                }

                // Parse den Zollsatz aus dem amount String
                BigDecimal rate = parseTariffRate(appliedMeasure.getAmount());
                if (rate != null && (lowestRate == null || rate.compareTo(lowestRate) < 0)) {
                    lowestRate = rate;
                }
            }
        }

        return lowestRate != null ? lowestRate : BigDecimal.ZERO;
    }

    private boolean isMeasureValid(AppliedMeasure measure, LocalDate date) {
        if (measure.getStartDate() != null && measure.getStartDate().isAfter(date)) {
            return false;
        }
        if (measure.getEndDate() != null && measure.getEndDate().isBefore(date)) {
            return false;
        }
        return true;
    }

    private boolean isCountryExcluded(AppliedMeasure measure, Geo country) {
        // Finde Exclusions für diese AppliedMeasure
        List<MeasureExclusion> exclusions =
                measureExclusionRepository.findByAppliedMeasure(
                        AggregateReference.to(measure.getId()));

        return exclusions.stream()
                .anyMatch(exc -> exc.getGeo() != null &&
                        exc.getGeo().getId().equals(country.getId()));
    }

    private BigDecimal parseTariffRate(String amount) {
        if (amount == null || amount.isEmpty()) {
            return null;
        }

        // Einfacher Parser für Prozentsätze
        // Format: "12.5 %" oder "12.5% + ..." oder "12.5 % MAX ..."
        Pattern pattern = Pattern.compile("^([0-9]+\\.?[0-9]*)\\s*%");
        Matcher matcher = pattern.matcher(amount);

        if (matcher.find()) {
            try {
                return new BigDecimal(matcher.group(1));
            } catch (NumberFormatException e) {
                log.warn("Could not parse tariff rate from: {}", amount);
            }
        }

        return null;
    }

    /**
     * Result-Klasse für Tarif-Abfragen
     */
    public static class TariffResult {
        private final boolean found;
        private final BigDecimal rate;
        private final String hsCode;
        private final String countryCode;
        private final String message;

        private TariffResult(boolean found, BigDecimal rate, String hsCode,
                             String countryCode, String message) {
            this.found = found;
            this.rate = rate;
            this.hsCode = hsCode;
            this.countryCode = countryCode;
            this.message = message;
        }

        public static TariffResult success(BigDecimal rate, String hsCode, String countryCode) {
            return new TariffResult(true, rate, hsCode, countryCode, null);
        }

        public static TariffResult notFound(String message) {
            return new TariffResult(false, null, null, null, message);
        }

        // Getters
        public boolean isFound() { return found; }
        public BigDecimal getRate() { return rate; }
        public String getHsCode() { return hsCode; }
        public String getCountryCode() { return countryCode; }
        public String getMessage() { return message; }
    }
}