package at.procon.eventhub.tachograph.service;

import at.procon.eventhub.dto.DriverCardRefDto;
import at.procon.eventhub.dto.DriverRefDto;
import at.procon.eventhub.dto.EventLifecycle;
import at.procon.eventhub.dto.EventType;
import at.procon.eventhub.dto.VehicleRefDto;
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
import at.procon.eventhub.importing.extraction.ExtractionContext;
import at.procon.eventhub.tachograph.dto.TachographImportRequest;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

final class TachographRawPayloadSupport {

    private TachographRawPayloadSupport() {
    }

    static Map<String, Object> baseRawPayload(
            ResultSet rs,
            ExtractionContext<TachographImportRequest> context,
            DriverRefDto driverRef,
            VehicleRefDto vehicleRef,
            EventType eventType,
            EventLifecycle lifecycle
    ) throws SQLException {
        Map<String, Object> raw = new LinkedHashMap<>();
        String sourceRowId = string(rs, "source_row_id");
        put(raw, "sourceRowId", sourceRowId);
        if (sourceRowId != null) {
            put(raw, "sourceRowIds", List.of(sourceRowId));
            put(raw, "intervalId", intervalId(context, sourceRowId));
        }
        put(raw, "sourceKind", context == null || context.planItem() == null ? null : context.planItem().sourceKind());
        put(raw, "extractionCode", context == null || context.planItem() == null ? null : context.planItem().extractionCode());
        put(raw, "level", "RAW_INTERVAL");
        put(raw, "eventType", eventType == null ? null : eventType.name());
        put(raw, "lifecycle", lifecycle == null ? null : lifecycle.name());

        enrichDriver(raw, driverRef);
        enrichVehicle(raw, vehicleRef);
        enrichSourcePackage(raw, rs);
        enrichOdometer(raw, rs);
        return raw;
    }

    static void put(Map<String, Object> target, String key, Object value) {
        if (value != null) {
            target.put(key, value instanceof Enum<?> enumValue ? enumValue.name() : value);
        }
    }

    private static String intervalId(ExtractionContext<TachographImportRequest> context, String sourceRowId) {
        String extractionCode = context == null || context.planItem() == null ? null : context.planItem().extractionCode();
        return "TACHOGRAPH:" + (extractionCode == null || extractionCode.isBlank() ? "UNKNOWN" : extractionCode) + ":" + sourceRowId;
    }

    private static void enrichDriver(Map<String, Object> raw, DriverRefDto driverRef) {
        if (driverRef == null || !driverRef.hasAnyReference()) {
            return;
        }
        put(raw, "driverKey", driverRef.stableKey());
        put(raw, "driverSourceEntityId", driverRef.sourceEntityId());
        DriverCardRefDto driverCard = driverRef.driverCard();
        if (driverCard != null && driverCard.hasValue()) {
            put(raw, "driverCardKey", driverCard.stableKey());
            put(raw, "driverCardNation", driverCard.nation());
            put(raw, "driverCardNationNumericCode", driverCard.nationNumericCode());
            put(raw, "driverCardNumber", driverCard.number());
        }
    }

    private static void enrichVehicle(Map<String, Object> raw, VehicleRefDto vehicleRef) {
        if (vehicleRef == null || !vehicleRef.hasAnyReference()) {
            return;
        }
        put(raw, "vehicleKey", vehicleRef.vin());
        put(raw, "vehicleSourceEntityId", vehicleRef.sourceVehicleEntityId());
        put(raw, "vehicleVin", vehicleRef.vin());
        put(raw, "vehicleRegistrationSourceEntityId", vehicleRef.sourceRegistrationEntityId());
        VehicleRegistrationRefDto registration = vehicleRef.vehicleRegistration();
        if (registration != null && registration.hasValue()) {
            put(raw, "registrationKey", registration.stableKey());
            put(raw, "vehicleRegistrationNation", registration.nation());
            put(raw, "vehicleRegistrationNationNumericCode", registration.nationNumericCode());
            put(raw, "vehicleRegistrationNumber", registration.number());
        }
    }

    private static void enrichSourcePackage(Map<String, Object> raw, ResultSet rs) throws SQLException {
        put(raw, "sourcePackageKind", string(rs, "source_package_kind"));
        put(raw, "sourcePackageId", string(rs, "source_package_id"));
        put(raw, "sourcePackageEntityId", string(rs, "source_package_entity_id"));
    }

    private static void enrichOdometer(Map<String, Object> raw, ResultSet rs) throws SQLException {
        Long odometerM = longValue(rs, "odometer_m");
        put(raw, "odometerM", odometerM);
        if (odometerM != null) {
            put(raw, "odometerKm", odometerM / 1_000L);
        }
    }

    private static String string(ResultSet rs, String column) throws SQLException {
        try {
            String value = rs.getString(column);
            return value == null || value.isBlank() ? null : value.trim();
        } catch (SQLException ex) {
            if (missingColumn(ex)) {
                return null;
            }
            throw ex;
        }
    }

    private static Long longValue(ResultSet rs, String column) throws SQLException {
        Object value;
        try {
            value = rs.getObject(column);
        } catch (SQLException ex) {
            if (missingColumn(ex)) {
                return null;
            }
            throw ex;
        }
        if (value == null) {
            return null;
        }
        if (value instanceof Number number) {
            return number.longValue();
        }
        return Long.parseLong(value.toString());
    }

    private static boolean missingColumn(SQLException ex) {
        String state = ex.getSQLState();
        String message = ex.getMessage();
        return "S0022".equals(state)
                || "42703".equals(state)
                || (message != null && message.toLowerCase(java.util.Locale.ROOT).contains("column")
                && (message.toLowerCase(java.util.Locale.ROOT).contains("not found")
                || message.toLowerCase(java.util.Locale.ROOT).contains("not valid")
                || message.toLowerCase(java.util.Locale.ROOT).contains("invalid")));
    }
}
