package de.avatic.taricdb.helper.workbook.converters;

import de.avatic.taricdb.helper.stats.HeapStat;
import de.avatic.taricdb.helper.workbook.ExcelTable;
import de.avatic.taricdb.helper.workbook.wrapper.AppliedMeasureConditionWrapper;
import de.avatic.taricdb.helper.workbook.wrapper.AppliedMeasureWrapper;
import de.avatic.taricdb.helper.workbook.wrapper.MeasureExclusionWrapper;
import de.avatic.taricdb.helper.workbook.wrapper.MeasureFootnoteWrapper;
import de.avatic.taricdb.model.*;
import de.avatic.taricdb.service.ReferenceStorageService;
import de.avatic.taricdb.service.SetupStatusService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jdbc.core.mapping.AggregateReference;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

public class AppliedMeasureConverter implements ExcelTableConverter<AppliedMeasureWrapper> {

    Logger log = LoggerFactory.getLogger(AppliedMeasureConverter.class);

    private ReferenceStorageService referenceStorage;
    private SetupStatusService statusService;
    private int recordOffset;
    private int count = 0;

    public AppliedMeasureConverter(ReferenceStorageService referenceStorage, SetupStatusService statusService, int recordCurrentCount) {
        this.referenceStorage = referenceStorage;
        this.statusService = statusService;
        this.recordOffset = recordCurrentCount;
    }

    @Override
    public Collection<AppliedMeasureWrapper> convert(ExcelTable table) {
        Collection<AppliedMeasureWrapper> appliedMeasures = new ArrayList<>();

        Collection<MeasureFootnoteWrapper> measureFootnoteWrappers = referenceStorage
                .getEntites(MeasureFootnoteWrapper.class);
        Collection<Measure> measures = referenceStorage.getEntites(Measure.class);
        Collection<MeasureExclusionWrapper> measureExclusions = referenceStorage
                .getEntites(MeasureExclusionWrapper.class);
        Collection<AppliedMeasureConditionWrapper> appliedMeasureConditions = referenceStorage
                .getEntites(AppliedMeasureConditionWrapper.class);

        Collection<LegalBase> legalBases = referenceStorage.getEntites(LegalBase.class);

        table.getRows().forEach((row) -> {
            count++;
            if (row.index() == 0) {
                return;
            }


            if (count % 10000 == 0) {
                int percentage = Math.round((float) (count * 100) / table.getRows().size());
                log.info("applied measures processed: " + percentage + "% - " + count + " of " + table.getRows().size() + " " + HeapStat.getHeapUtilization(), true, null);
                statusService.setStatus(recordOffset, "Convert applied measures ... " + percentage + "% ", true, null);
            }

            String hsCode = row.getAs("Goods code", String.class);

            if (hsCode == null || hsCode.isEmpty()) {
                return;
            }

            String origin = row.getAs("Origin code", String.class);
            String addCode = row.getAs("Add code", String.class);
            Integer measureCode = row.getAs("Meas. type code", Integer.class);
            String measureCodeStr = row.getAs("Meas. type code", String.class);
            String legalBase = row.getAs("Legal base", String.class);
            String orderNumber = row.getAs("Order No.", String.class);
            Integer orderNumberInt = row.getAs("Order No.", Integer.class);

            LocalDate startDate = row.getAs("Start date", LocalDate.class);
            LocalDate endDate = row.getAs("End date", LocalDate.class);
            String amount = row.getAs("Duty", String.class);

            Measure measure = measures.stream().filter(c -> c.getMeasureCode().equals(measureCodeStr)).findAny()
                    .orElse(null);

            if (null == measure) {
                throw new RuntimeException("Measure not found: " + row.getAs("Meas. type code", String.class));
            }

            Set<MeasureFootnote> foundMeasureFootnotes = measureFootnoteWrappers.stream()
                    .filter(s -> Objects.equals(s.getMeasureCode(), measureCode)
                            && Objects.equals(s.getHsCode(), hsCode)
                            && Objects.equals(s.getOrigin(), origin)
                            && Objects.equals(s.getAddCode(), addCode)
                            && Objects.equals(s.getOrderNumber(), orderNumber))
                    .map(MeasureFootnoteWrapper::getMeasureFootnote).collect(Collectors.toSet());

            Set<MeasureExclusion> foundMeasureExclusions = null;

            if (isNumeric(origin)) {
                Integer originCode = Integer.parseInt(origin);
                foundMeasureExclusions = measureExclusions.stream()
                        .filter(s -> Objects.equals(s.getMeasureCode(), measureCode)
                                && Objects.equals(s.getHsCode(), hsCode)
                                && Objects.equals(s.getOriginCode(), originCode)
                                && Objects.equals(s.getAddCode(), addCode)
                                && Objects.equals(s.getOrderNumber(), orderNumber))
                        .map(MeasureExclusionWrapper::getMeasureExclusion).collect(Collectors.toSet());
            }


            Set<AppliedMeasureCondition> foundMeasureConditions = appliedMeasureConditions.stream()
                    .filter(s -> Objects.equals(s.getHsCode(), hsCode)
                            && Objects.equals(s.getOriginCode(), origin)
                            && Objects.equals(s.getAdditionalCode(), addCode)
                            && Objects.equals(s.getOrderNumber(), orderNumber)
                            && Objects.equals(s.getMeasureCode(), measureCode))
                    .map(AppliedMeasureConditionWrapper::getAppliedMeasureCondition)
                    .collect(Collectors.toSet());

            LegalBase foundLegalBase = legalBases.stream().filter(s -> s.getLegalBase().equals(legalBase)).findAny()
                    .orElse(null);

            if (foundLegalBase == null) {
                throw new RuntimeException("Legal base not found: " + legalBase);
            }

            AppliedMeasureWrapper appliedMeasure = new AppliedMeasureWrapper(
                    new AppliedMeasure(AggregateReference.to(measure.getId()), foundMeasureFootnotes,
                            AggregateReference.to(foundLegalBase.getId()), legalBase, foundMeasureExclusions,
                            foundMeasureConditions, startDate, endDate, amount, orderNumberInt),
                    hsCode, addCode, origin);

            appliedMeasures.add(appliedMeasure);

        });

        return appliedMeasures;
    }


    private boolean isNumeric(String str) {
        return str.matches("-?\\d+(\\.\\d+)?"); // match a number with optional '-' and decimal.
    }


}
