package at.procon.eventhub.importing;

import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.dto.AcquisitionStrategy;
import at.procon.eventhub.dto.ImportMode;
import at.procon.eventhub.dto.ImportScopeDto;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.function.Supplier;

public abstract class AbstractConfiguredImportPlanService<R extends ImportRunRequest, D> {

    private final Supplier<List<EventHubProperties.ConfiguredImportPlan>> planSupplier;
    private final String providerName;

    protected AbstractConfiguredImportPlanService(
            Supplier<List<EventHubProperties.ConfiguredImportPlan>> planSupplier,
            String providerName
    ) {
        this.planSupplier = planSupplier;
        this.providerName = providerName;
    }

    public List<D> listPlans() {
        return planSupplier.get().stream()
                .map(this::toDto)
                .toList();
    }

    public D getPlan(String planKey) {
        return planByKey(planKey);
    }

    public R createRequest(String planKey, ImportMode modeOverride, AcquisitionStrategy strategyOverride) {
        EventHubProperties.ConfiguredImportPlan plan = rawPlanByKey(planKey);
        return createRequest(plan, modeOverride, strategyOverride, false);
    }

    public R createScheduledRequest(EventHubProperties.ConfiguredImportPlan plan) {
        return createRequest(plan, plan.getScheduledMode(), plan.getScheduledStrategy(), false);
    }

    public R createInitialRequest(EventHubProperties.ConfiguredImportPlan plan) {
        return createRequest(plan, plan.getInitialMode(), plan.getInitialStrategy(), true);
    }

    protected abstract D toDto(EventHubProperties.ConfiguredImportPlan plan);

    protected abstract R buildRequest(
            EventHubProperties.ConfiguredImportPlan plan,
            ImportMode mode,
            AcquisitionStrategy strategy,
            ImportScopeDto scope
    );

    private R createRequest(
            EventHubProperties.ConfiguredImportPlan plan,
            ImportMode modeOverride,
            AcquisitionStrategy strategyOverride,
            boolean applyInitialOccurredWindow
    ) {
        ImportMode mode = modeOverride == null ? plan.getScheduledMode() : modeOverride;
        AcquisitionStrategy strategy = strategyOverride == null
                ? (mode == ImportMode.INCREMENTAL_UPDATE ? plan.getScheduledStrategy() : plan.getInitialStrategy())
                : strategyOverride;
        return buildRequest(plan, mode, strategy, scopedForRequest(plan, mode, strategy, applyInitialOccurredWindow));
    }

    private ImportScopeDto scopedForRequest(
            EventHubProperties.ConfiguredImportPlan plan,
            ImportMode mode,
            AcquisitionStrategy strategy,
            boolean applyInitialOccurredWindow
    ) {
        ImportScopeDto scope = plan.getImportScope();
        if (applyInitialOccurredWindow && scope != null
                && (plan.getInitialOccurredFrom() != null || plan.getInitialOccurredTo() != null)) {
            return new ImportScopeDto(
                    scope.type(),
                    scope.rootSourceOrganisation(),
                    scope.includeChildren(),
                    plan.getInitialOccurredFrom() == null ? scope.occurredFrom() : plan.getInitialOccurredFrom(),
                    plan.getInitialOccurredTo() == null ? scope.occurredTo() : plan.getInitialOccurredTo()
            );
        }
        if (mode == ImportMode.INCREMENTAL_UPDATE
                && isWatermarkStrategy(strategy)
                && scope != null
                && scope.occurredFrom() == null
                && plan.getInitialOccurredFrom() != null) {
            return new ImportScopeDto(
                    scope.type(),
                    scope.rootSourceOrganisation(),
                    scope.includeChildren(),
                    plan.getInitialOccurredFrom(),
                    scope.occurredTo() == null ? plan.getInitialOccurredTo() : scope.occurredTo()
            );
        }
        return scope;
    }

    private boolean isWatermarkStrategy(AcquisitionStrategy strategy) {
        return strategy == AcquisitionStrategy.SOURCE_PACKAGE_WATERMARK
                || strategy == AcquisitionStrategy.SOURCE_ROW_WATERMARK;
    }

    private D planByKey(String planKey) {
        return toDto(rawPlanByKey(planKey));
    }

    private EventHubProperties.ConfiguredImportPlan rawPlanByKey(String planKey) {
        return planSupplier.get().stream()
                .filter(plan -> normalize(plan.getPlanKey()).equals(normalize(planKey)))
                .findFirst()
                .orElseThrow(() -> new NoSuchElementException("No configured " + providerName + " import plan found for key " + planKey));
    }

    protected ConfiguredImportPlanDto genericDto(EventHubProperties.ConfiguredImportPlan plan) {
        return new ConfiguredImportPlanDto(
                plan.getPlanKey(),
                plan.isEnabled(),
                plan.getCron(),
                plan.getTenantKey(),
                plan.getEventSource(),
                plan.getSourceGroup(),
                plan.getImportScope(),
                plan.getEventFamilies(),
                plan.getInitialMode(),
                plan.getScheduledMode(),
                plan.getInitialStrategy(),
                plan.getScheduledStrategy(),
                plan.isRefreshMasterDataFirst(),
                plan.getInitialOccurredFrom(),
                plan.getInitialOccurredTo(),
                plan.isRunInitialOnStartup()
        );
    }

    private String normalize(String value) {
        return value == null ? "" : value.trim().toLowerCase(Locale.ROOT);
    }
}
