package de.votian.services.service;

import de.votian.services.config.ParameterKeys;
import de.votian.services.entity.Address;
import de.votian.services.entity.Company;
import de.votian.services.entity.CostCenter;
import de.votian.services.entity.CourierVehicle;
import de.votian.services.entity.Customer;
import de.votian.services.entity.Job;
import de.votian.services.entity.ServiceZipCode;
import de.votian.services.entity.Tour;
import de.votian.services.entity.VehicleDisposition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Service
@Transactional(readOnly = true)
public class LonghaulRemoteDbService {

    private static final int DEFAULT_DISTANCE_KM = 80;

    private final ParameterService parameterService;
    private final ConnectionFactory connectionFactory;

    public LonghaulRemoteDbService(ParameterService parameterService) {
        this(parameterService, new DriverManagerConnectionFactory());
    }

    LonghaulRemoteDbService(ParameterService parameterService, ConnectionFactory connectionFactory) {
        this.parameterService = parameterService;
        this.connectionFactory = connectionFactory;
    }

    public boolean isConfigured() {
        return parseAccessData(parameterService.getParameterValueWithFallback(
                ParameterKeys.LONGHAUL_REMOTE_DB_ACCESSDATA,
                0L
        )) != null;
    }

    public RemoteSnapshot loadSnapshot(Long requestedHeadquartersId) {
        RemoteDbAccessData accessData = requireAccessData();
        try (Connection connection = connectionFactory.open(accessData)) {
            Long effectiveHeadquartersId = resolveEffectiveHeadquartersId(connection, requestedHeadquartersId);
            boolean mediationActive = "1".equals(loadParameterWithFallback(
                    connection,
                    ParameterKeys.LONGHAUL_ACTIVE_REMOTE_DB,
                    effectiveHeadquartersId
            ));
            int minimumDistanceKm = parseInt(
                    loadParameterWithFallback(connection, ParameterKeys.LONGHAUL_KM, effectiveHeadquartersId),
                    DEFAULT_DISTANCE_KM
            );

            List<Job> jobs = loadJobs(connection, effectiveHeadquartersId);
            Map<Long, List<Tour>> toursByJobId = loadTours(connection, jobs);
            Map<Long, Address> addresses = loadAddresses(connection, toursByJobId.values());
            Map<String, ServiceZipCode> zipcodes = loadZipcodes(connection, addresses.values());
            Map<Long, CostCenter> costCenters = loadCostCenters(connection, jobs, toursByJobId);
            Map<Long, String> customerNames = loadCustomerNames(connection, costCenters.values());
            Map<Long, VehicleDisposition> dispositions = loadDispositions(connection, jobs);
            Map<Long, CourierVehicle> vehicles = loadVehicles(connection, dispositions.values());
            Map<Integer, String> vehicleTypes = loadVehicleTypes(connection);

            return new RemoteSnapshot(
                    effectiveHeadquartersId,
                    mediationActive,
                    minimumDistanceKm,
                    jobs,
                    toursByJobId,
                    addresses,
                    zipcodes,
                    costCenters,
                    customerNames,
                    dispositions,
                    vehicles,
                    vehicleTypes
            );
        } catch (SQLException e) {
            throw new IllegalStateException("Remote-DB konnte nicht gelesen werden: " + e.getMessage(), e);
        }
    }

    public RemoteJobDetailSnapshot loadJobDetailSnapshot(Long requestedHeadquartersId, Long jobId) {
        if (jobId == null) {
            throw new IllegalArgumentException("Auftrag fehlt.");
        }
        RemoteDbAccessData accessData = requireAccessData();
        try (Connection connection = connectionFactory.open(accessData)) {
            Long effectiveHeadquartersId = resolveEffectiveHeadquartersId(connection, requestedHeadquartersId);
            Job job = loadJob(connection, effectiveHeadquartersId, jobId);
            Map<Long, List<Tour>> toursByJobId = loadTours(connection, List.of(job));
            Map<Long, Address> addresses = loadAddresses(connection, toursByJobId.values());
            Map<String, ServiceZipCode> zipcodes = loadZipcodes(connection, addresses.values());
            Map<Long, CostCenter> costCenters = loadCostCenters(connection, List.of(job), toursByJobId);
            Map<Long, String> customerNames = loadCustomerNames(connection, costCenters.values());
            Map<Long, VehicleDisposition> dispositions = loadDispositions(connection, List.of(job));
            Map<Long, CourierVehicle> vehicles = loadVehicles(connection, dispositions.values());
            Map<Integer, String> vehicleTypes = loadVehicleTypes(connection);
            RemoteCourierStatus courierStatus = loadCourierStatus(connection, job.getCourierId());
            return new RemoteJobDetailSnapshot(
                    effectiveHeadquartersId,
                    job,
                    toursByJobId,
                    addresses,
                    zipcodes,
                    costCenters,
                    customerNames,
                    dispositions,
                    vehicles,
                    vehicleTypes,
                    courierStatus
            );
        } catch (SQLException e) {
            throw new IllegalStateException("Remote-DB konnte nicht gelesen werden: " + e.getMessage(), e);
        }
    }

    @Transactional
    public void returnJobToHeadquarters(Long requestedHeadquartersId, Long jobId) {
        if (jobId == null) {
            throw new IllegalArgumentException("Auftrag fehlt.");
        }
        RemoteDbAccessData accessData = requireAccessData();
        try (Connection connection = connectionFactory.open(accessData)) {
            Long effectiveHeadquartersId = resolveEffectiveHeadquartersId(connection, requestedHeadquartersId);
            try (PreparedStatement statement = connection.prepareStatement(
                    "UPDATE job SET jb_longhaul = 2 WHERE jb_id = ? AND hq_id = ? AND jb_longhaul = 1"
            )) {
                statement.setLong(1, jobId);
                statement.setLong(2, effectiveHeadquartersId);
                if (statement.executeUpdate() == 0) {
                    throw new IllegalArgumentException(
                            "Der Auftrag kann nicht zurueckgegeben werden, da er manuell als Ferntour markiert wurde."
                    );
                }
            }
        } catch (SQLException e) {
            throw new IllegalStateException("Remote-DB konnte nicht aktualisiert werden: " + e.getMessage(), e);
        }
    }

    private RemoteDbAccessData requireAccessData() {
        RemoteDbAccessData accessData = parseAccessData(parameterService.getParameterValueWithFallback(
                ParameterKeys.LONGHAUL_REMOTE_DB_ACCESSDATA,
                0L
        ));
        if (accessData == null) {
            throw new IllegalStateException("LONGHAUL_REMOTE_DB_ACCESSDATA ist nicht gepflegt.");
        }
        return accessData;
    }

    private RemoteDbAccessData parseAccessData(String rawValue) {
        if (rawValue == null || rawValue.isBlank()) {
            return null;
        }
        String[] parts = rawValue.split("\\|", 4);
        if (parts.length < 4) {
            return null;
        }
        String host = parts[0].trim();
        String user = parts[1].trim();
        String password = parts[2];
        String database = parts[3].trim();
        if (host.isBlank() || user.isBlank() || database.isBlank()) {
            return null;
        }
        return new RemoteDbAccessData(host, user, password, database);
    }

    private Long resolveEffectiveHeadquartersId(Connection connection, Long requestedHeadquartersId) throws SQLException {
        if (requestedHeadquartersId != null && headquartersExists(connection, requestedHeadquartersId)) {
            return requestedHeadquartersId;
        }
        try (PreparedStatement statement = connection.prepareStatement("SELECT MIN(hq_id) FROM headquarters");
             ResultSet resultSet = statement.executeQuery()) {
            if (resultSet.next()) {
                return nullableLong(resultSet, 1);
            }
        }
        throw new IllegalStateException("In der Remote-DB wurde keine Niederlassung gefunden.");
    }

    private boolean headquartersExists(Connection connection, Long headquartersId) throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT 1 FROM headquarters WHERE hq_id = ? LIMIT 1"
        )) {
            statement.setLong(1, headquartersId);
            try (ResultSet resultSet = statement.executeQuery()) {
                return resultSet.next();
            }
        }
    }

    private String loadParameterWithFallback(Connection connection, String key, Long headquartersId) throws SQLException {
        String value = loadParameter(connection, key, headquartersId != null ? headquartersId : 0L);
        if ((value == null || value.isBlank()) && headquartersId != null && headquartersId != 0L) {
            value = loadParameter(connection, key, 0L);
        }
        return value != null ? value : "";
    }

    private String loadParameter(Connection connection, String key, long headquartersId) throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT par_value FROM parameter WHERE par_key = ? AND hq_id = ? AND emp_id = 0 ORDER BY par_id ASC LIMIT 1"
        )) {
            statement.setString(1, key);
            statement.setLong(2, headquartersId);
            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    return resultSet.getString(1);
                }
            }
        }
        return "";
    }

    private List<Job> loadJobs(Connection connection, Long headquartersId) throws SQLException {
        List<Job> jobs = new ArrayList<>();
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT jb_id, jb_status, jb_ordertime, jb_taketime, jb_finishtime, cr_id, cr_sid, "
                        + "jb_totalprice, jb_longhaul, vht_id, jb_storno, csc_id_payer, csc_id_related "
                        + "FROM job "
                        + "WHERE hq_id = ? AND jb_status IN (1, 2, 9) AND COALESCE(jb_storno, '0') <> '2' "
                        + "ORDER BY jb_ordertime ASC, jb_id ASC"
        )) {
            statement.setLong(1, headquartersId);
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    Job job = new Job();
                    job.setId(resultSet.getLong("jb_id"));
                    job.setHeadquartersId(headquartersId);
                    job.setStatus(nullableInteger(resultSet, "jb_status"));
                    job.setOrderTime(toLocalDateTime(resultSet.getTimestamp("jb_ordertime")));
                    job.setTakeTime(toLocalDateTime(resultSet.getTimestamp("jb_taketime")));
                    job.setFinishTime(toLocalDateTime(resultSet.getTimestamp("jb_finishtime")));
                    job.setCourierId(nullableLong(resultSet, "cr_id"));
                    job.setCourierSid(resultSet.getString("cr_sid"));
                    job.setTotalPrice(resultSet.getBigDecimal("jb_totalprice"));
                    job.setLonghaul(nullableInteger(resultSet, "jb_longhaul"));
                    job.setVehicleTypeId(nullableInteger(resultSet, "vht_id"));
                    job.setStorno(resultSet.getString("jb_storno"));
                    job.setCostCenterPayerId(nullableLong(resultSet, "csc_id_payer"));
                    job.setCostCenterRelatedId(nullableLong(resultSet, "csc_id_related"));
                    jobs.add(job);
                }
            }
        }
        return jobs;
    }

    private Job loadJob(Connection connection, Long headquartersId, Long jobId) throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT jb_id, hq_id, jb_status, jb_ordertime, jb_taketime, jb_finishtime, cr_id, cr_sid, "
                        + "jb_totalprice, jb_longhaul, vht_id, jb_storno, csc_id_payer, csc_id_related, jb_tourname "
                        + "FROM job WHERE jb_id = ? AND hq_id = ? AND COALESCE(jb_storno, '0') <> '2' LIMIT 1"
        )) {
            statement.setLong(1, jobId);
            statement.setLong(2, headquartersId);
            try (ResultSet resultSet = statement.executeQuery()) {
                if (!resultSet.next()) {
                    throw new IllegalArgumentException("Auftrag nicht gefunden: " + jobId);
                }
                Job job = new Job();
                job.setId(resultSet.getLong("jb_id"));
                job.setHeadquartersId(nullableLong(resultSet, "hq_id"));
                job.setStatus(nullableInteger(resultSet, "jb_status"));
                job.setOrderTime(toLocalDateTime(resultSet.getTimestamp("jb_ordertime")));
                job.setTakeTime(toLocalDateTime(resultSet.getTimestamp("jb_taketime")));
                job.setFinishTime(toLocalDateTime(resultSet.getTimestamp("jb_finishtime")));
                job.setCourierId(nullableLong(resultSet, "cr_id"));
                job.setCourierSid(resultSet.getString("cr_sid"));
                job.setTotalPrice(resultSet.getBigDecimal("jb_totalprice"));
                job.setLonghaul(nullableInteger(resultSet, "jb_longhaul"));
                job.setVehicleTypeId(nullableInteger(resultSet, "vht_id"));
                job.setStorno(resultSet.getString("jb_storno"));
                job.setCostCenterPayerId(nullableLong(resultSet, "csc_id_payer"));
                job.setCostCenterRelatedId(nullableLong(resultSet, "csc_id_related"));
                job.setTourName(resultSet.getString("jb_tourname"));
                return job;
            }
        }
    }

    private Map<Long, List<Tour>> loadTours(Connection connection, List<Job> jobs) throws SQLException {
        if (jobs.isEmpty()) {
            return Map.of();
        }
        List<Long> jobIds = jobs.stream().map(Job::getId).toList();
        Map<Long, List<Tour>> toursByJobId = new LinkedHashMap<>();
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT tr_id, jb_id, tr_sort, ad_id, tr_comp, tr_person, tr_hsno, tr_commission_no, csc_id, tr_phone, tr_status, tr_remark "
                        + "FROM tour WHERE jb_id IN (" + placeholders(jobIds.size()) + ") ORDER BY jb_id ASC, tr_sort ASC"
        )) {
            bindLongs(statement, jobIds);
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    Tour tour = new Tour();
                    tour.setId(resultSet.getLong("tr_id"));
                    tour.setJobId(resultSet.getLong("jb_id"));
                    tour.setSort(nullableInteger(resultSet, "tr_sort"));
                    tour.setAddressId(nullableLong(resultSet, "ad_id"));
                    tour.setComp(resultSet.getString("tr_comp"));
                    tour.setPerson(resultSet.getString("tr_person"));
                    tour.setHsno(resultSet.getString("tr_hsno"));
                    tour.setCommissionNo(resultSet.getString("tr_commission_no"));
                    tour.setCostCenterId(nullableLong(resultSet, "csc_id"));
                    tour.setPhone(resultSet.getString("tr_phone"));
                    tour.setStatus(nullableInteger(resultSet, "tr_status"));
                    tour.setRemark(resultSet.getString("tr_remark"));
                    toursByJobId.computeIfAbsent(tour.getJobId(), ignored -> new ArrayList<>()).add(tour);
                }
            }
        }
        return toursByJobId;
    }

    private Map<Long, Address> loadAddresses(Connection connection, Collection<List<Tour>> groupedTours) throws SQLException {
        Set<Long> addressIds = new LinkedHashSet<>();
        for (List<Tour> tours : groupedTours) {
            for (Tour tour : tours) {
                if (tour.getAddressId() != null) {
                    addressIds.add(tour.getAddressId());
                }
            }
        }
        if (addressIds.isEmpty()) {
            return Map.of();
        }
        Map<Long, Address> addresses = new HashMap<>();
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT ad_id, ad_street, ad_zipcode, ad_city, ad_country "
                        + "FROM address WHERE ad_id IN (" + placeholders(addressIds.size()) + ")"
        )) {
            bindLongs(statement, new ArrayList<>(addressIds));
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    Address address = new Address();
                    address.setId(resultSet.getLong("ad_id"));
                    address.setStreet(resultSet.getString("ad_street"));
                    address.setZipcode(resultSet.getString("ad_zipcode"));
                    address.setCity(resultSet.getString("ad_city"));
                    address.setCountry(resultSet.getString("ad_country"));
                    addresses.put(address.getId(), address);
                }
            }
        }
        return addresses;
    }

    private Map<String, ServiceZipCode> loadZipcodes(Connection connection, Collection<Address> addresses) throws SQLException {
        Set<String> zipcodes = new LinkedHashSet<>();
        for (Address address : addresses) {
            String zipcode = normalizeZipcode(address.getZipcode());
            if (zipcode != null) {
                zipcodes.add(zipcode);
            }
        }
        if (zipcodes.isEmpty()) {
            return Map.of();
        }
        Map<String, ServiceZipCode> result = new HashMap<>();
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT srvp_id, srvp_plz, hq_id, srvp_valid, srvp_latitude, srvp_longitude "
                        + "FROM serviceplz WHERE srvp_plz IN (" + placeholders(zipcodes.size()) + ")"
        )) {
            bindStrings(statement, new ArrayList<>(zipcodes));
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    ServiceZipCode serviceZipCode = new ServiceZipCode();
                    serviceZipCode.setId(resultSet.getLong("srvp_id"));
                    serviceZipCode.setZipcode(resultSet.getString("srvp_plz"));
                    serviceZipCode.setHeadquartersId(nullableLong(resultSet, "hq_id"));
                    serviceZipCode.setValid(resultSet.getString("srvp_valid"));
                    serviceZipCode.setLatitude(nullableDouble(resultSet, "srvp_latitude"));
                    serviceZipCode.setLongitude(nullableDouble(resultSet, "srvp_longitude"));
                    if (serviceZipCode.getZipcode() != null) {
                        result.put(serviceZipCode.getZipcode(), serviceZipCode);
                    }
                }
            }
        }
        return result;
    }

    private Map<Long, CostCenter> loadCostCenters(Connection connection,
                                                  List<Job> jobs,
                                                  Map<Long, List<Tour>> toursByJobId) throws SQLException {
        Set<Long> costCenterIds = new LinkedHashSet<>();
        for (Job job : jobs) {
            if (job.getCostCenterPayerId() != null) {
                costCenterIds.add(job.getCostCenterPayerId());
            }
            if (job.getCostCenterRelatedId() != null) {
                costCenterIds.add(job.getCostCenterRelatedId());
            }
            List<Tour> tours = toursByJobId.getOrDefault(job.getId(), List.of());
            for (Tour tour : tours) {
                if (tour.getCostCenterId() != null) {
                    costCenterIds.add(tour.getCostCenterId());
                }
            }
        }
        if (costCenterIds.isEmpty()) {
            return Map.of();
        }
        Map<Long, CostCenter> result = new HashMap<>();
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT csc_id, cs_id, csc_name FROM costcenter WHERE csc_id IN (" + placeholders(costCenterIds.size()) + ")"
        )) {
            bindLongs(statement, new ArrayList<>(costCenterIds));
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    CostCenter costCenter = new CostCenter();
                    costCenter.setId(resultSet.getLong("csc_id"));
                    costCenter.setCustomerId(nullableLong(resultSet, "cs_id"));
                    costCenter.setName(resultSet.getString("csc_name"));
                    result.put(costCenter.getId(), costCenter);
                }
            }
        }
        return result;
    }

    private Map<Long, String> loadCustomerNames(Connection connection, Collection<CostCenter> costCenters) throws SQLException {
        Set<Long> customerIds = new LinkedHashSet<>();
        for (CostCenter costCenter : costCenters) {
            if (costCenter.getCustomerId() != null) {
                customerIds.add(costCenter.getCustomerId());
            }
        }
        if (customerIds.isEmpty()) {
            return Map.of();
        }

        Map<Long, Customer> customers = new HashMap<>();
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT cs_id, cs_eid, cmp_id FROM customer WHERE cs_id IN (" + placeholders(customerIds.size()) + ")"
        )) {
            bindLongs(statement, new ArrayList<>(customerIds));
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    Customer customer = new Customer();
                    customer.setId(resultSet.getLong("cs_id"));
                    customer.setEid(resultSet.getString("cs_eid"));
                    customer.setCompanyId(nullableLong(resultSet, "cmp_id"));
                    customers.put(customer.getId(), customer);
                }
            }
        }

        Set<Long> companyIds = new LinkedHashSet<>();
        for (Customer customer : customers.values()) {
            if (customer.getCompanyId() != null) {
                companyIds.add(customer.getCompanyId());
            }
        }

        Map<Long, Company> companies = new HashMap<>();
        if (!companyIds.isEmpty()) {
            try (PreparedStatement statement = connection.prepareStatement(
                    "SELECT cmp_id, cmp_comp FROM company WHERE cmp_id IN (" + placeholders(companyIds.size()) + ")"
            )) {
                bindLongs(statement, new ArrayList<>(companyIds));
                try (ResultSet resultSet = statement.executeQuery()) {
                    while (resultSet.next()) {
                        Company company = new Company();
                        company.setId(resultSet.getLong("cmp_id"));
                        company.setComp(resultSet.getString("cmp_comp"));
                        companies.put(company.getId(), company);
                    }
                }
            }
        }

        Map<Long, String> result = new HashMap<>();
        for (Customer customer : customers.values()) {
            Company company = companies.get(customer.getCompanyId());
            String name = company != null && company.getComp() != null && !company.getComp().isBlank()
                    ? company.getComp()
                    : customer.getEid();
            result.put(customer.getId(), name);
        }
        return result;
    }

    private Map<Long, VehicleDisposition> loadDispositions(Connection connection, List<Job> jobs) throws SQLException {
        if (jobs.isEmpty()) {
            return Map.of();
        }
        List<Long> jobIds = jobs.stream().map(Job::getId).toList();
        Map<Long, VehicleDisposition> result = new HashMap<>();
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT vhd_id, crvh_id, jb_id FROM vehicledisposition WHERE jb_id IN (" + placeholders(jobIds.size()) + ")"
        )) {
            bindLongs(statement, jobIds);
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    VehicleDisposition disposition = new VehicleDisposition();
                    disposition.setId(resultSet.getLong("vhd_id"));
                    disposition.setCourierVehicleId(nullableLong(resultSet, "crvh_id"));
                    disposition.setJobId(resultSet.getLong("jb_id"));
                    result.put(disposition.getJobId(), disposition);
                }
            }
        }
        return result;
    }

    private Map<Long, CourierVehicle> loadVehicles(Connection connection,
                                                   Collection<VehicleDisposition> dispositions) throws SQLException {
        Set<Long> vehicleIds = new LinkedHashSet<>();
        for (VehicleDisposition disposition : dispositions) {
            if (disposition.getCourierVehicleId() != null) {
                vehicleIds.add(disposition.getCourierVehicleId());
            }
        }
        if (vehicleIds.isEmpty()) {
            return Map.of();
        }
        Map<Long, CourierVehicle> result = new HashMap<>();
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT crvh_id, crvh_sid, vht_id FROM couriervehicle WHERE crvh_id IN (" + placeholders(vehicleIds.size()) + ")"
        )) {
            bindLongs(statement, new ArrayList<>(vehicleIds));
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    CourierVehicle vehicle = new CourierVehicle();
                    vehicle.setId(resultSet.getLong("crvh_id"));
                    vehicle.setSid(resultSet.getString("crvh_sid"));
                    vehicle.setVehicleTypeId(nullableInteger(resultSet, "vht_id"));
                    result.put(vehicle.getId(), vehicle);
                }
            }
        }
        return result;
    }

    private Map<Integer, String> loadVehicleTypes(Connection connection) throws SQLException {
        Map<Integer, String> result = new HashMap<>();
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT mt_sort, mt_value FROM metatype WHERE mt_type = ? ORDER BY mt_sort ASC"
        )) {
            statement.setString(1, "vehicletype");
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    Integer sort = nullableInteger(resultSet, "mt_sort");
                    if (sort != null) {
                        result.put(sort, resultSet.getString("mt_value"));
                    }
                }
            }
        }
        return result;
    }

    private RemoteCourierStatus loadCourierStatus(Connection connection, Long courierId) throws SQLException {
        if (courierId == null) {
            return null;
        }
        try (PreparedStatement statement = connection.prepareStatement(
                "SELECT cr_gps_long, cr_gps_lat, cr_gps_time, cr_gps_type, cr_locationzipcode, cr_availabletime "
                        + "FROM courier WHERE cr_id = ? LIMIT 1"
        )) {
            statement.setLong(1, courierId);
            try (ResultSet resultSet = statement.executeQuery()) {
                if (!resultSet.next()) {
                    return null;
                }
                return new RemoteCourierStatus(
                        nullableDouble(resultSet, "cr_gps_lat"),
                        nullableDouble(resultSet, "cr_gps_long"),
                        toLocalDateTime(resultSet.getTimestamp("cr_gps_time")),
                        nullableInteger(resultSet, "cr_gps_type"),
                        resultSet.getString("cr_locationzipcode"),
                        toLocalDateTime(resultSet.getTimestamp("cr_availabletime"))
                );
            }
        }
    }

    private void bindLongs(PreparedStatement statement, List<Long> values) throws SQLException {
        for (int index = 0; index < values.size(); index++) {
            statement.setLong(index + 1, values.get(index));
        }
    }

    private void bindStrings(PreparedStatement statement, List<String> values) throws SQLException {
        for (int index = 0; index < values.size(); index++) {
            statement.setString(index + 1, values.get(index));
        }
    }

    private String placeholders(int count) {
        return String.join(",", Collections.nCopies(count, "?"));
    }

    private String normalizeZipcode(String zipcode) {
        if (zipcode == null) {
            return null;
        }
        String trimmed = zipcode.trim();
        return trimmed.isBlank() ? null : trimmed;
    }

    private int parseInt(String value, int fallback) {
        try {
            return value != null && !value.isBlank() ? Integer.parseInt(value.trim()) : fallback;
        } catch (NumberFormatException e) {
            return fallback;
        }
    }

    private LocalDateTime toLocalDateTime(Timestamp timestamp) {
        return timestamp != null ? timestamp.toLocalDateTime() : null;
    }

    private Long nullableLong(ResultSet resultSet, String columnName) throws SQLException {
        long value = resultSet.getLong(columnName);
        return resultSet.wasNull() ? null : value;
    }

    private Long nullableLong(ResultSet resultSet, int columnIndex) throws SQLException {
        long value = resultSet.getLong(columnIndex);
        return resultSet.wasNull() ? null : value;
    }

    private Integer nullableInteger(ResultSet resultSet, String columnName) throws SQLException {
        int value = resultSet.getInt(columnName);
        return resultSet.wasNull() ? null : value;
    }

    private Double nullableDouble(ResultSet resultSet, String columnName) throws SQLException {
        double value = resultSet.getDouble(columnName);
        return resultSet.wasNull() ? null : value;
    }

    public record RemoteSnapshot(Long effectiveHeadquartersId,
                                 boolean mediationActive,
                                 int minimumDistanceKm,
                                 List<Job> jobs,
                                 Map<Long, List<Tour>> toursByJobId,
                                 Map<Long, Address> addresses,
                                 Map<String, ServiceZipCode> zipcodes,
                                 Map<Long, CostCenter> costCenters,
                                 Map<Long, String> customerNames,
                                 Map<Long, VehicleDisposition> dispositions,
                                 Map<Long, CourierVehicle> vehicles,
                                 Map<Integer, String> vehicleTypes) {
    }

    public record RemoteJobDetailSnapshot(Long effectiveHeadquartersId,
                                          Job job,
                                          Map<Long, List<Tour>> toursByJobId,
                                          Map<Long, Address> addresses,
                                          Map<String, ServiceZipCode> zipcodes,
                                          Map<Long, CostCenter> costCenters,
                                          Map<Long, String> customerNames,
                                          Map<Long, VehicleDisposition> dispositions,
                                          Map<Long, CourierVehicle> vehicles,
                                          Map<Integer, String> vehicleTypes,
                                          RemoteCourierStatus courierStatus) {
    }

    public record RemoteCourierStatus(Double latitude,
                                      Double longitude,
                                      LocalDateTime gpsTime,
                                      Integer gpsType,
                                      String locationZipcode,
                                      LocalDateTime availableTime) {
    }

    interface ConnectionFactory {
        Connection open(RemoteDbAccessData accessData) throws SQLException;
    }

    static final class DriverManagerConnectionFactory implements ConnectionFactory {
        @Override
        public Connection open(RemoteDbAccessData accessData) throws SQLException {
            return DriverManager.getConnection(
                    "jdbc:mariadb://" + accessData.host() + "/" + accessData.database(),
                    accessData.user(),
                    accessData.password()
            );
        }
    }

    record RemoteDbAccessData(String host, String user, String password, String database) {
    }
}
