package at.procon.eventhub.processing.service;

import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.dto.EventHubEventDto;
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.yellowfox.dto.YellowFoxD8BookingDto;
import at.procon.eventhub.yellowfox.service.YellowFoxD8BookingEventMapper;
import at.procon.eventhub.yellowfox.service.YellowFoxD8BookingRowMapper;
import at.procon.eventhub.yellowfox.service.YellowFoxD8IgnitionTransitionDetector;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Qualifier;
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
@ConditionalOnExpression("T(org.springframework.util.StringUtils).hasText('${eventhub.yellow-fox.datasource.jdbc-url:}')")
public class YellowFoxDbRuntimeEventLoader implements RuntimeDriverEventLoader, RuntimeVehicleEventLoader {

    private static final String SYNTHETIC_NATION = "YELLOWFOX";

    private final NamedParameterJdbcTemplate jdbcTemplate;
    private final ResourceLoader resourceLoader;
    private final YellowFoxD8BookingRowMapper rowMapper;
    private final YellowFoxD8BookingEventMapper eventMapper;
    private final YellowFoxD8IgnitionTransitionDetector ignitionTransitionDetector;
    private final EventHubProperties properties;

    public YellowFoxDbRuntimeEventLoader(
            @Qualifier("yellowFoxNamedParameterJdbcTemplate") NamedParameterJdbcTemplate jdbcTemplate,
            ResourceLoader resourceLoader,
            YellowFoxD8BookingRowMapper rowMapper,
            YellowFoxD8BookingEventMapper eventMapper,
            YellowFoxD8IgnitionTransitionDetector ignitionTransitionDetector,
            EventHubProperties properties
    ) {
        this.jdbcTemplate = jdbcTemplate;
        this.resourceLoader = resourceLoader;
        this.rowMapper = rowMapper;
        this.eventMapper = eventMapper;
        this.ignitionTransitionDetector = ignitionTransitionDetector;
        this.properties = properties;
    }

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

    @Override
    public List<EventHubEventDto> loadDriverEvents(UnifiedRuntimeProcessingRequest request) {
        if (request.driverCardNation() != null && !SYNTHETIC_NATION.equalsIgnoreCase(request.driverCardNation())) {
            return List.of();
        }
        return query(request, null, request.occurredFrom(), request.occurredTo());
    }

    @Override
    public List<EventHubEventDto> loadVehicleEvents(
            UnifiedRuntimeProcessingRequest request,
            UnifiedDiscoveredVehicleRef vehicleRef
    ) {
        if (vehicleRef.registrationNation() != null && !SYNTHETIC_NATION.equalsIgnoreCase(vehicleRef.registrationNation())) {
            return List.of();
        }
        return query(request, vehicleRef, request.vehicleOccurredFrom(), request.vehicleOccurredTo());
    }

    private List<EventHubEventDto> query(
            UnifiedRuntimeProcessingRequest request,
            UnifiedDiscoveredVehicleRef vehicleRef,
            OffsetDateTime occurredFrom,
            OffsetDateTime occurredTo
    ) {
        String sql = applyFilters(loadSqlTemplate(), filters(request, vehicleRef, occurredFrom, occurredTo));
        Map<String, Object> params = parameters(request, vehicleRef, occurredFrom, occurredTo);
        List<EventHubEventDto> events = new ArrayList<>();
        YellowFoxD8IgnitionTransitionDetector.Session ignitionSession =
                ignitionTransitionDetector.newSession(properties.getYellowFox().isEmitInitialIgnitionSnapshot());

        jdbcTemplate.query(sql, params, rs -> {
            while (rs.next()) {
                YellowFoxD8BookingDto booking = rowMapper.map(rs, request.tenantKey(), null, null);
                if (!hasEventReference(booking)) {
                    continue;
                }
                events.add(eventMapper.map(booking));
                EventHubEventDto ignitionEvent = ignitionSession.detect(booking);
                if (ignitionEvent != null) {
                    events.add(ignitionEvent);
                }
            }
            return null;
        });
        return List.copyOf(events);
    }

    private String filters(
            UnifiedRuntimeProcessingRequest request,
            UnifiedDiscoveredVehicleRef vehicleRef,
            OffsetDateTime occurredFrom,
            OffsetDateTime occurredTo
    ) {
        StringBuilder filters = new StringBuilder("where 1 = 1");
        if (occurredFrom != null) {
            filters.append("\n  and b.utc >= :occurredFrom");
        }
        if (occurredTo != null) {
            filters.append("\n  and b.utc < :occurredTo");
        }
        if (request.driverSourceEntityId() != null) {
            filters.append("\n  and cast(b.driver_id as varchar(128)) = :driverSourceEntityId");
        }
        if (request.driverCardNumber() != null) {
            filters.append("\n  and left(trim(d.drivers_card), 14) = :driverCardNumber");
        }
        if (vehicleRef != null) {
            if (vehicleRef.sourceVehicleEntityId() != null) {
                filters.append("\n  and cast(b.vehicle_id as varchar(128)) = :vehicleSourceEntityId");
            }
            if (vehicleRef.vin() != null) {
                filters.append("\n  and v.vin = :vehicleVin");
            }
            if (vehicleRef.registrationNumber() != null) {
                filters.append("\n  and v.vrn = :vehicleRegistrationNumber");
            }
        }
        return filters.toString();
    }

    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("driverSourceEntityId", request.driverSourceEntityId());
        params.put("driverCardNumber", request.driverCardNumber());
        params.put("vehicleSourceEntityId", vehicleRef == null ? null : vehicleRef.sourceVehicleEntityId());
        params.put("vehicleVin", vehicleRef == null ? null : vehicleRef.vin());
        params.put("vehicleRegistrationNumber", vehicleRef == null ? null : vehicleRef.registrationNumber());
        return params;
    }

    private boolean hasEventReference(YellowFoxD8BookingDto booking) {
        return (booking.driverRef() != null && booking.driverRef().hasAnyReference())
                || (booking.vehicleRef() != null && booking.vehicleRef().hasAnyReference());
    }

    private String applyFilters(String sqlTemplate, String filters) {
        if (!sqlTemplate.contains("/*__FILTERS__*/")) {
            throw new IllegalStateException("YellowFox D8 runtime SQL template is missing filter marker");
        }
        return sqlTemplate.replace("/*__FILTERS__*/", filters);
    }

    private String loadSqlTemplate() {
        Resource resource = resourceLoader.getResource("classpath:sql/yellowfox/d8-bookings.sql");
        try (var in = resource.getInputStream()) {
            return StreamUtils.copyToString(in, StandardCharsets.UTF_8);
        } catch (IOException ex) {
            throw new IllegalStateException("Failed to load YellowFox D8 runtime SQL", ex);
        }
    }
}
