package at.procon.eventhub.processing.service;

import at.procon.eventhub.dto.AcquisitionStrategy;
import at.procon.eventhub.dto.EventFamily;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.dto.EventHubPackageRequest;
import at.procon.eventhub.dto.EventSourceDto;
import at.procon.eventhub.dto.ImportMode;
import at.procon.eventhub.dto.ImportScopeDto;
import at.procon.eventhub.importing.ImportPlanItemDto;
import at.procon.eventhub.importing.ImportTimeChunkDto;
import at.procon.eventhub.importing.extraction.ExtractionContext;
import at.procon.eventhub.importing.extraction.ExtractionDefinition;
import at.procon.eventhub.processing.model.UnifiedDiscoveredVehicleRef;
import at.procon.eventhub.processing.model.UnifiedEventSourceFamily;
import at.procon.eventhub.processing.model.UnifiedRuntimeEventBackend;
import at.procon.eventhub.processing.model.UnifiedRuntimeProcessingRequest;
import at.procon.eventhub.reference.TachographNationRegistry;
import at.procon.eventhub.tachograph.dto.TachographImportRequest;
import at.procon.eventhub.tachograph.service.TachographExtractionDefinitionRegistry;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

@Component
@ConditionalOnBean(name = "tachographNamedParameterJdbcTemplate")
@ConditionalOnExpression("T(org.springframework.util.StringUtils).hasText('${eventhub.tachograph.datasource.jdbc-url:}')")
public class TachographDbRuntimeEventLoader implements RuntimeDriverEventLoader, RuntimeVehicleEventLoader {

    private final NamedParameterJdbcTemplate jdbcTemplate;
    private final TachographExtractionDefinitionRegistry definitionRegistry;
    private final ResourceLoader resourceLoader;

    public TachographDbRuntimeEventLoader(
            @Qualifier("tachographNamedParameterJdbcTemplate") NamedParameterJdbcTemplate jdbcTemplate,
            TachographExtractionDefinitionRegistry definitionRegistry,
            ResourceLoader resourceLoader
    ) {
        this.jdbcTemplate = jdbcTemplate;
        this.definitionRegistry = definitionRegistry;
        this.resourceLoader = resourceLoader;
    }

    @Override
    public boolean supports(UnifiedRuntimeProcessingRequest request, UnifiedEventSourceFamily sourceFamily) {
        return request.eventBackend() == UnifiedRuntimeEventBackend.SOURCE_DB
                && sourceFamily == UnifiedEventSourceFamily.TACHOGRAPH_DB;
    }

    @Override
    public List<EventHubEventDto> loadDriverEvents(UnifiedRuntimeProcessingRequest request) {
        List<EventHubEventDto> result = new ArrayList<>();
        for (ExtractionDefinition<TachographImportRequest> definition : driverDefinitions()) {
            result.addAll(queryDefinition(
                    definition,
                    request,
                    null,
                    request.occurredFrom(),
                    request.occurredTo()
            ));
        }
        return List.copyOf(result);
    }

    @Override
    public List<EventHubEventDto> loadVehicleEvents(
            UnifiedRuntimeProcessingRequest request,
            UnifiedDiscoveredVehicleRef vehicleRef
    ) {
        List<EventHubEventDto> result = new ArrayList<>();
        for (ExtractionDefinition<TachographImportRequest> definition : vehicleDefinitions()) {
            result.addAll(queryDefinition(
                    definition,
                    request,
                    vehicleRef,
                    request.vehicleOccurredFrom(),
                    request.vehicleOccurredTo()
            ));
        }
        return List.copyOf(result);
    }

    private List<EventHubEventDto> queryDefinition(
            ExtractionDefinition<TachographImportRequest> definition,
            UnifiedRuntimeProcessingRequest request,
            UnifiedDiscoveredVehicleRef vehicleRef,
            OffsetDateTime occurredFrom,
            OffsetDateTime occurredTo
    ) {
        List<EventHubEventDto> events = new ArrayList<>();
        String sql = wrapSql(loadSql(definition.sqlResource()), request, vehicleRef);
        Map<String, Object> params = parameters(request, vehicleRef, occurredFrom, occurredTo);
        ExtractionContext<TachographImportRequest> context = context(definition, request, occurredFrom, occurredTo);
        jdbcTemplate.query(sql, params, rs -> {
            int rowNum = 0;
            while (rs.next()) {
                events.add(definition.rowMapper().map(rs, rowNum, context));
                rowNum++;
            }
            return null;
        });
        return events;
    }

    private List<ExtractionDefinition<TachographImportRequest>> driverDefinitions() {
        return definitionRegistry.definitions().stream()
                .filter(definition -> !"VEHICLE".equals(definition.entityAxis()))
                .toList();
    }

    private List<ExtractionDefinition<TachographImportRequest>> vehicleDefinitions() {
        return definitionRegistry.definitions().stream()
                .filter(definition -> !"DRIVER".equals(definition.entityAxis()))
                .toList();
    }

    private ExtractionContext<TachographImportRequest> context(
            ExtractionDefinition<TachographImportRequest> definition,
            UnifiedRuntimeProcessingRequest request,
            OffsetDateTime occurredFrom,
            OffsetDateTime occurredTo
    ) {
        EventSourceDto eventSource = eventSourceFor(definition.sourceKind());
        ImportScopeDto scope = ImportScopeDto.tenantAll(occurredFrom, occurredTo);
        TachographImportRequest importRequest = new TachographImportRequest(
                request.tenantKey(),
                eventSource,
                null,
                scope,
                EnumSet.of(definition.eventFamily()),
                ImportMode.REPROCESS,
                false,
                AcquisitionStrategy.OCCURRED_AT_WINDOW_WITH_OVERLAP
        );
        ImportPlanItemDto planItem = new ImportPlanItemDto(
                definition.eventFamily(),
                definition.sourceKind(),
                definition.code(),
                List.of(),
                definition.entityAxis(),
                "Runtime direct tachograph load",
                AcquisitionStrategy.OCCURRED_AT_WINDOW_WITH_OVERLAP
        );
        ImportTimeChunkDto chunk = new ImportTimeChunkDto(1, occurredFrom, occurredTo);
        EventHubPackageRequest packageInfo = new EventHubPackageRequest(
                request.tenantKey(),
                eventSource,
                null,
                scope,
                definition.eventFamily().name(),
                occurredFrom == null ? null : occurredFrom.toLocalDate(),
                "RUNTIME:TACHOGRAPH:" + definition.code() + ":" + UUID.randomUUID()
        );
        return new ExtractionContext<>(
                UUID.randomUUID(),
                UUID.randomUUID(),
                0,
                importRequest,
                planItem,
                chunk,
                eventSource,
                packageInfo
        );
    }

    private EventSourceDto eventSourceFor(String sourceKind) {
        String sourceKey = switch (sourceKind) {
            case "VEHICLE_UNIT" -> "TACHOGRAPH_VEHICLE_UNIT";
            case "DRIVER_CARD" -> "TACHOGRAPH_DRIVER_CARD";
            default -> "TACHOGRAPH_" + sourceKind;
        };
        return new EventSourceDto("TACHOGRAPH", sourceKind, sourceKey, null, null, null);
    }

    private Map<String, Object> parameters(
            UnifiedRuntimeProcessingRequest request,
            UnifiedDiscoveredVehicleRef vehicleRef,
            OffsetDateTime occurredFrom,
            OffsetDateTime occurredTo
    ) {
        Map<String, Object> params = new HashMap<>();
        params.put("occurredFrom", occurredFrom);
        params.put("occurredTo", occurredTo);
        params.put("organisationId", null);
        params.put("sourcePackageWatermarkEnabled", 0);
        params.put("lastSourcePackageImportedAt", null);
        params.put("lastSourcePackageIdNumeric", null);
        params.put("driverSourceEntityId", request.driverSourceEntityId());
        params.put("driverCardNation", alphaNation(request.driverCardNation()));
        params.put("driverCardNumber", request.driverCardNumber());
        params.put("vehicleSourceEntityId", vehicleRef == null ? null : vehicleRef.sourceVehicleEntityId());
        params.put("vehicleVin", vehicleRef == null ? null : vehicleRef.vin());
        params.put("vehicleRegistrationNation", vehicleRef == null ? null : alphaNation(vehicleRef.registrationNation()));
        params.put("vehicleRegistrationNumber", vehicleRef == null ? null : vehicleRef.registrationNumber());
        return params;
    }

    private String wrapSql(
            String baseSql,
            UnifiedRuntimeProcessingRequest request,
            UnifiedDiscoveredVehicleRef vehicleRef
    ) {
        StringBuilder sql = new StringBuilder("select * from (");
        sql.append(baseSql);
        sql.append("\n) runtime where 1 = 1");
        if (request.driverSourceEntityId() != null) {
            sql.append("\n  and runtime.driver_source_entity_id = :driverSourceEntityId");
        }
        if (request.driverCardNumber() != null) {
            sql.append("\n  and runtime.driver_card_number = :driverCardNumber");
            if (request.driverCardNation() != null) {
                sql.append("\n  and runtime.driver_card_nation = :driverCardNation");
            }
        }
        if (vehicleRef != null) {
            if (vehicleRef.sourceVehicleEntityId() != null) {
                sql.append("\n  and runtime.vehicle_source_entity_id = :vehicleSourceEntityId");
            }
            if (vehicleRef.vin() != null) {
                sql.append("\n  and runtime.vehicle_vin = :vehicleVin");
            }
            if (vehicleRef.registrationNumber() != null) {
                sql.append("\n  and runtime.vehicle_registration_number = :vehicleRegistrationNumber");
                if (vehicleRef.registrationNation() != null) {
                    sql.append("\n  and runtime.vehicle_registration_nation = :vehicleRegistrationNation");
                }
            }
        }
        sql.append("\norder by runtime.occurred_at, runtime.external_source_event_id");
        return sql.toString();
    }

    private String loadSql(String sqlResource) {
        Resource resource = resourceLoader.getResource(sqlResource);
        try (var in = resource.getInputStream()) {
            return StreamUtils.copyToString(in, StandardCharsets.UTF_8);
        } catch (IOException ex) {
            throw new IllegalStateException("Failed to load tachograph runtime SQL " + sqlResource, ex);
        }
    }

    private String alphaNation(String rawNation) {
        return TachographNationRegistry.alphaCode(rawNation, null);
    }
}
