package at.procon.eventhub.yellowfox.service;

import at.procon.eventhub.dto.DriverCardRefDto;
import at.procon.eventhub.dto.DriverRefDto;
import at.procon.eventhub.dto.VehicleRefDto;
import at.procon.eventhub.dto.VehicleRegistrationRefDto;
import at.procon.eventhub.reference.DriverCardNumberNormalizer;
import at.procon.eventhub.yellowfox.dto.YellowFoxD8BookingDto;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.stereotype.Component;

@Component
public class YellowFoxD8BookingRowMapper {

    private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
    };

    private final ObjectMapper objectMapper;

    public YellowFoxD8BookingRowMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    public YellowFoxD8BookingDto map(ResultSet rs, String tenantKey, String sourceInstanceKey, String tenantProviderSettingKey) throws SQLException {
        String eventId = string(rs, "eventid");
        OffsetDateTime occurredAt = rs.getObject("utc", OffsetDateTime.class);
        Integer vehicleId = getInteger(rs, "vehicle_id");
        Integer driverId = getInteger(rs, "driver_id");
        String vehicleVrn = string(rs, "vehicle_vrn");
        String vehicleVin = string(rs, "vehicle_vin");
        String driverCard = normalizeBookingDriverCardNumber(string(rs, "driver_card_number"));
        Integer fleetId = getInteger(rs, "fleet_id");
        String fleetName = string(rs, "fleet_name");
        Integer odometer = getInteger(rs, "odometer");

        Map<String, Object> payload = trimPayloadStrings(payload(rs.getString("payload")));
        put(payload, "yellowFoxEventId", eventId);
        put(payload, "yellowFoxOdometerRaw", odometer);
        put(payload, "vehicleVrn", vehicleVrn);
        put(payload, "vehicleVin", vehicleVin);
        put(payload, "driverCardNumber", driverCard);
        put(payload, "driverFirstName", string(rs, "driver_firstname"));
        put(payload, "driverLastName", string(rs, "driver_lastname"));
        put(payload, "fleetId", fleetId);
        put(payload, "fleetName", fleetName);
        put(payload, "telematicProviderId", getInteger(rs, "telematic_provider_id"));
        put(payload, "telematicProviderName", string(rs, "telematic_provider_name"));

        DriverRefDto driverRef = driverId == null && isBlank(driverCard)
                ? null
                : new DriverRefDto(
                        driverId == null ? null : driverId.toString(),
                        new DriverCardRefDto(YellowFoxReferenceSemantics.SYNTHETIC_REFERENCE_NATION, driverCard)
                );
        VehicleRefDto vehicleRef = vehicleId == null && isBlank(vehicleVin) && isBlank(vehicleVrn)
                ? null
                : new VehicleRefDto(
                        vehicleId == null ? null : vehicleId.toString(),
                        vehicleVin,
                        vehicleId == null ? null : vehicleId.toString(),
                        new VehicleRegistrationRefDto(YellowFoxReferenceSemantics.SYNTHETIC_REFERENCE_NATION, vehicleVrn)
                );

        return new YellowFoxD8BookingDto(
                tenantKey,
                sourceInstanceKey,
                tenantProviderSettingKey,
                fleetId == null ? null : fleetId.toString(),
                fleetName,
                eventId,
                string(rs, "key"),
                getInteger(rs, "ignition"),
                getInteger(rs, "previous_ignition"),
                getInteger(rs, "eventtype"),
                getInteger(rs, "state"),
                occurredAt,
                null,
                driverRef,
                vehicleRef,
                odometer == null ? null : odometer.longValue() * 1000L,
                rs.getBigDecimal("latitude"),
                rs.getBigDecimal("longitude"),
                payload
        );
    }

    private Integer getInteger(ResultSet rs, String column) throws SQLException {
        int value = rs.getInt(column);
        return rs.wasNull() ? null : value;
    }

    private Map<String, Object> payload(String json) {
        if (json == null || json.isBlank()) {
            return new LinkedHashMap<>();
        }
        try {
            return new LinkedHashMap<>(objectMapper.readValue(json, MAP_TYPE));
        } catch (Exception ex) {
            Map<String, Object> fallback = new LinkedHashMap<>();
            fallback.put("rawPayload", json);
            fallback.put("payloadParseError", ex.getMessage());
            return fallback;
        }
    }

    private String string(ResultSet rs, String column) throws SQLException {
        String value = rs.getString(column);
        return value == null || value.isBlank() ? null : value.trim();
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> trimPayloadStrings(Map<String, Object> payload) {
        payload.replaceAll((key, value) -> trimPayloadValue(value));
        return payload;
    }

    @SuppressWarnings("unchecked")
    private Object trimPayloadValue(Object value) {
        if (value instanceof String text) {
            return text.trim();
        }
        if (value instanceof Map<?, ?> nestedMap) {
            ((Map<Object, Object>) nestedMap).replaceAll((key, nestedValue) -> trimPayloadValue(nestedValue));
            return nestedMap;
        }
        if (value instanceof java.util.List<?> list) {
            for (int i = 0; i < list.size(); i++) {
                ((java.util.List<Object>) list).set(i, trimPayloadValue(list.get(i)));
            }
            return list;
        }
        return value;
    }

    private void put(Map<String, Object> target, String key, Object value) {
        if (value != null) {
            target.put(key, value instanceof BigDecimal bd ? bd.stripTrailingZeros().toPlainString() : value);
        }
    }

    private boolean isBlank(String value) {
        return value == null || value.isBlank();
    }

    private String normalizeBookingDriverCardNumber(String value) {
        return DriverCardNumberNormalizer.canonical(value);
    }
}
