package de.votian.services.service;

import de.votian.services.config.ParameterKeys;
import de.votian.services.dto.LonghaulDashboardDto;
import de.votian.services.dto.LonghaulJobDetailDto;
import de.votian.services.dto.LonghaulJobRowDto;
import de.votian.services.entity.Address;
import de.votian.services.entity.CostCenter;
import de.votian.services.entity.Courier;
import de.votian.services.entity.Job;
import de.votian.services.entity.ServiceZipCode;
import de.votian.services.entity.Tour;
import de.votian.services.repository.AddressRepository;
import de.votian.services.repository.CompanyRepository;
import de.votian.services.repository.CostCenterRepository;
import de.votian.services.repository.CourierRepository;
import de.votian.services.repository.CourierVehicleRepository;
import de.votian.services.repository.CustomerRepository;
import de.votian.services.repository.JobRepository;
import de.votian.services.repository.MetaTypeRepository;
import de.votian.services.repository.ServiceZipCodeRepository;
import de.votian.services.repository.TourRepository;
import de.votian.services.repository.VehicleDispositionRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.within;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@SuppressWarnings("null")
class LonghaulServiceTest {

    private JobRepository jobRepository;
    private TourRepository tourRepository;
    private AddressRepository addressRepository;
    private ServiceZipCodeRepository serviceZipCodeRepository;
    private VehicleDispositionRepository vehicleDispositionRepository;
    private MetaTypeRepository metaTypeRepository;
    private CourierRepository courierRepository;
    private ParameterService parameterService;
    private LonghaulRemoteDbService longhaulRemoteDbService;
    private LonghaulService longhaulService;

    @BeforeEach
    void setUp() {
        jobRepository = mock(JobRepository.class);
        tourRepository = mock(TourRepository.class);
        addressRepository = mock(AddressRepository.class);
        serviceZipCodeRepository = mock(ServiceZipCodeRepository.class);
        vehicleDispositionRepository = mock(VehicleDispositionRepository.class);
        metaTypeRepository = mock(MetaTypeRepository.class);
        courierRepository = mock(CourierRepository.class);
        parameterService = mock(ParameterService.class);
        longhaulRemoteDbService = mock(LonghaulRemoteDbService.class);

        longhaulService = new LonghaulService(
                jobRepository,
                tourRepository,
                addressRepository,
                mock(CostCenterRepository.class),
                mock(CustomerRepository.class),
                mock(CompanyRepository.class),
                courierRepository,
                serviceZipCodeRepository,
                vehicleDispositionRepository,
                mock(CourierVehicleRepository.class),
                metaTypeRepository,
                parameterService,
                longhaulRemoteDbService
        );

        when(vehicleDispositionRepository.findByJobIdIn(anyList())).thenReturn(List.of());
        when(metaTypeRepository.findByTypeOrderBySort("vehicletype")).thenReturn(List.of());
    }

    @Test
    void getDashboardAddsMapCoordinatesAndAirDistanceForGeocodedLonghaulJobs() {
        Job job = job(1L, 9, 0, LocalDateTime.of(2026, 3, 27, 9, 0));
        Tour pickup = tour(1L, 1, 11L, "Start A", "KOMM-1");
        Tour delivery = tour(1L, 2, 12L, "Ziel B", null);
        Address pickupAddress = address(11L, "20095", "Hamburg");
        Address deliveryAddress = address(12L, "10115", "Berlin");

        when(parameterService.getParameterValueWithFallback(ParameterKeys.LONGHAUL_ACTIVE, 1L)).thenReturn("0");
        when(parameterService.getParameterValueWithFallback(ParameterKeys.LONGHAUL_KM, 1L)).thenReturn("80");
        when(jobRepository.findByHeadquartersIdAndStatusInOrderByOrderTimeAscIdAsc(1L, List.of(1, 2, 9)))
                .thenReturn(List.of(job));
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(1L))).thenReturn(List.of(pickup, delivery));
        when(addressRepository.findAllById(any())).thenReturn(List.of(pickupAddress, deliveryAddress));
        when(serviceZipCodeRepository.findByZipcodeIn(any())).thenReturn(List.of(
                zipcode("20095", 53.5500d, 10.0000d),
                zipcode("10115", 52.5200d, 13.4050d)
        ));

        LonghaulDashboardDto result = longhaulService.getDashboard(1L);

        assertThat(result.getPendingJobs()).hasSize(1);
        LonghaulJobRowDto row = result.getPendingJobs().getFirst();
        assertThat(row.getPickupLatitude()).isEqualTo(53.5500d);
        assertThat(row.getPickupLongitude()).isEqualTo(10.0000d);
        assertThat(row.getDeliveryLatitude()).isEqualTo(52.5200d);
        assertThat(row.getDeliveryLongitude()).isEqualTo(13.4050d);
        assertThat(row.getAirDistanceKm()).isCloseTo(313.7d, within(0.2d));
        assertThat(row.getCommissionNo()).isEqualTo("KOMM-1");
    }

    @Test
    void getDashboardKeepsManualLonghaulRowsVisibleWithoutMapCoordinatesInMediationMode() {
        Job job = job(2L, 9, 3, LocalDateTime.of(2026, 3, 27, 10, 0));
        Tour pickup = tour(2L, 1, 21L, "Start C", null);
        Tour delivery = tour(2L, 2, 22L, "Ziel D", null);

        when(parameterService.getParameterValueWithFallback(ParameterKeys.LONGHAUL_ACTIVE, 1L)).thenReturn("1");
        when(parameterService.getParameterValueWithFallback(ParameterKeys.LONGHAUL_KM, 1L)).thenReturn("80");
        when(jobRepository.findByHeadquartersIdAndStatusInOrderByOrderTimeAscIdAsc(1L, List.of(1, 2, 9)))
                .thenReturn(List.of(job));
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(2L))).thenReturn(List.of(pickup, delivery));
        when(addressRepository.findAllById(any())).thenReturn(List.of(
                address(21L, "ABCDE", "Ohne Geo"),
                address(22L, "FGHIJ", "Ohne Geo")
        ));
        when(serviceZipCodeRepository.findByZipcodeIn(any())).thenReturn(List.of());

        LonghaulDashboardDto result = longhaulService.getDashboard(1L);

        assertThat(result.getPendingJobs()).hasSize(1);
        LonghaulJobRowDto row = result.getPendingJobs().getFirst();
        assertThat(row.isManualLonghaul()).isTrue();
        assertThat(row.getPickupLatitude()).isNull();
        assertThat(row.getDeliveryLatitude()).isNull();
        assertThat(row.getAirDistanceKm()).isNull();
        assertThat(row.isReturnable()).isFalse();
    }

    @Test
    void getDashboardUsesRemoteSnapshotWhenRequested() {
        Job job = job(3L, 9, 1, LocalDateTime.of(2026, 3, 27, 11, 0));
        job.setCostCenterPayerId(7L);
        job.setVehicleTypeId(4);
        Tour pickup = tour(3L, 1, 31L, "Start E", "KOMM-3");
        Tour delivery = tour(3L, 2, 32L, "Ziel F", null);
        Address pickupAddress = address(31L, "50667", "Koeln");
        Address deliveryAddress = address(32L, "60311", "Frankfurt");
        CostCenter costCenter = new CostCenter();
        costCenter.setId(7L);
        costCenter.setCustomerId(8L);
        costCenter.setName("Remote KST");

        when(longhaulRemoteDbService.loadSnapshot(1L)).thenReturn(new LonghaulRemoteDbService.RemoteSnapshot(
                5L,
                true,
                140,
                List.of(job),
                Map.of(3L, List.of(pickup, delivery)),
                Map.of(31L, pickupAddress, 32L, deliveryAddress),
                Map.of(
                        "50667", zipcode("50667", 50.9381d, 6.9599d),
                        "60311", zipcode("60311", 50.1109d, 8.6821d)
                ),
                Map.of(7L, costCenter),
                Map.of(8L, "Remote Kunde"),
                Map.of(),
                Map.of(),
                Map.of(4, "Transporter")
        ));

        LonghaulDashboardDto result = longhaulService.getDashboard(1L, true);

        assertThat(result.isRemoteDbActive()).isTrue();
        assertThat(result.getEffectiveHeadquartersId()).isEqualTo(5L);
        assertThat(result.isMediationActive()).isTrue();
        assertThat(result.getMinimumDistanceKm()).isEqualTo(140);
        assertThat(result.getPendingJobs()).hasSize(1);
        assertThat(result.getPendingJobs().getFirst().getCustomerName()).isEqualTo("Remote Kunde");
        assertThat(result.getPendingJobs().getFirst().getCostCenterName()).isEqualTo("Remote KST");
    }

    @Test
    void getJobDetailIncludesLocalLocatingAndStopMetadata() {
        Job job = job(4L, 1, 1, LocalDateTime.of(2026, 3, 27, 12, 0));
        job.setHeadquartersId(1L);
        job.setCourierId(77L);
        job.setTourName("FT Hamburg");
        job.setCostCenterPayerId(7L);

        Tour pickup = tour(4L, 1, 41L, "Start G", "KOMM-4");
        pickup.setPerson("Max Mustermann");
        pickup.setPhone("040 12345");
        pickup.setRemark("Rampe 2");
        pickup.setHsno("12a");
        Tour delivery = tour(4L, 2, 42L, "Ziel H", null);
        delivery.setPerson("Erika Muster");
        delivery.setPhone("030 98765");
        delivery.setRemark("Bitte avisieren");

        Address pickupAddress = address(41L, "20095", "Hamburg");
        pickupAddress.setStreet("Spitalerstrasse");
        pickupAddress.setCountry("DE");
        Address deliveryAddress = address(42L, "10115", "Berlin");
        deliveryAddress.setStreet("Invalidenstrasse");
        deliveryAddress.setCountry("DE");

        Courier courier = new Courier();
        courier.setId(77L);
        courier.setGpsLatitude(53.5511d);
        courier.setGpsLongitude(10.0011d);
        courier.setGpsTime(LocalDateTime.of(2026, 3, 27, 12, 15));
        courier.setGpsType(2);
        courier.setLocationZipcode("20095");
        courier.setAvailableTime(LocalDateTime.of(2026, 3, 27, 12, 5));

        when(jobRepository.findById(4L)).thenReturn(java.util.Optional.of(job));
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(4L))).thenReturn(List.of(pickup, delivery));
        when(addressRepository.findAllById(any())).thenReturn(List.of(pickupAddress, deliveryAddress));
        when(serviceZipCodeRepository.findByZipcodeIn(any())).thenReturn(List.of(
                zipcode("20095", 53.5500d, 10.0000d),
                zipcode("10115", 52.5200d, 13.4050d)
        ));
        when(courierRepository.findById(77L)).thenReturn(java.util.Optional.of(courier));

        LonghaulJobDetailDto result = longhaulService.getJobDetail(1L, 4L, false);

        assertThat(result.isRemoteDbActive()).isFalse();
        assertThat(result.getTourName()).isEqualTo("FT Hamburg");
        assertThat(result.getPickupStreet()).isEqualTo("Spitalerstrasse 12a");
        assertThat(result.getPickupRemark()).isEqualTo("Rampe 2");
        assertThat(result.getDeliveryPerson()).isEqualTo("Erika Muster");
        assertThat(result.getLocatingLatitude()).isEqualTo(53.5511d);
        assertThat(result.getLocatingLongitude()).isEqualTo(10.0011d);
        assertThat(result.getLocatingTypeLabel()).isEqualTo("GPS");
        assertThat(result.getLocatingZipcode()).isEqualTo("20095");
    }

    @Test
    void getJobDetailUsesRemoteSnapshotWhenRequested() {
        Job job = job(5L, 9, 3, LocalDateTime.of(2026, 3, 27, 13, 0));
        job.setTourName("Remote FT");
        job.setCostCenterPayerId(17L);
        Tour pickup = tour(5L, 1, 51L, "Remote Start", "KOMM-5");
        Tour delivery = tour(5L, 2, 52L, "Remote Ziel", null);
        Address pickupAddress = address(51L, "50667", "Koeln");
        pickupAddress.setStreet("Domkloster");
        Address deliveryAddress = address(52L, "60311", "Frankfurt");
        deliveryAddress.setStreet("Zeil");
        CostCenter costCenter = new CostCenter();
        costCenter.setId(17L);
        costCenter.setCustomerId(19L);
        costCenter.setName("Remote KST");

        when(longhaulRemoteDbService.loadJobDetailSnapshot(1L, 5L)).thenReturn(
                new LonghaulRemoteDbService.RemoteJobDetailSnapshot(
                        5L,
                        job,
                        Map.of(5L, List.of(pickup, delivery)),
                        Map.of(51L, pickupAddress, 52L, deliveryAddress),
                        Map.of(
                                "50667", zipcode("50667", 50.9381d, 6.9599d),
                                "60311", zipcode("60311", 50.1109d, 8.6821d)
                        ),
                        Map.of(17L, costCenter),
                        Map.of(19L, "Remote Kunde"),
                        Map.of(),
                        Map.of(),
                        Map.of(),
                        new LonghaulRemoteDbService.RemoteCourierStatus(
                                50.1100d,
                                8.6800d,
                                LocalDateTime.of(2026, 3, 27, 13, 15),
                                1,
                                "60311",
                                LocalDateTime.of(2026, 3, 27, 13, 10)
                        )
                )
        );

        LonghaulJobDetailDto result = longhaulService.getJobDetail(1L, 5L, true);

        assertThat(result.isRemoteDbActive()).isTrue();
        assertThat(result.getEffectiveHeadquartersId()).isEqualTo(5L);
        assertThat(result.getCustomerName()).isEqualTo("Remote Kunde");
        assertThat(result.getPickupStreet()).isEqualTo("Domkloster");
        assertThat(result.getLocatingTypeLabel()).isEqualTo("LBS");
        assertThat(result.getLocatingZipcode()).isEqualTo("60311");
    }

    private Job job(Long id, Integer status, Integer longhaul, LocalDateTime orderTime) {
        Job job = new Job();
        job.setId(id);
        job.setHeadquartersId(1L);
        job.setStatus(status);
        job.setLonghaul(longhaul);
        job.setOrderTime(orderTime);
        job.setStorno("0");
        job.setTotalPrice(new BigDecimal("99.00"));
        return job;
    }

    private Tour tour(Long jobId, Integer sort, Long addressId, String comp, String commissionNo) {
        Tour tour = new Tour();
        tour.setJobId(jobId);
        tour.setSort(sort);
        tour.setAddressId(addressId);
        tour.setComp(comp);
        tour.setCommissionNo(commissionNo);
        return tour;
    }

    private Address address(Long id, String zipcode, String city) {
        Address address = new Address();
        address.setId(id);
        address.setZipcode(zipcode);
        address.setCity(city);
        return address;
    }

    private ServiceZipCode zipcode(String zipcode, double latitude, double longitude) {
        ServiceZipCode serviceZipCode = new ServiceZipCode();
        serviceZipCode.setZipcode(zipcode);
        serviceZipCode.setLatitude(latitude);
        serviceZipCode.setLongitude(longitude);
        return serviceZipCode;
    }
}
