package de.votian.services.service;

import de.votian.services.dto.TrackingDashboardDto;
import de.votian.services.dto.TrackingJobRowDto;
import de.votian.services.entity.Address;
import de.votian.services.entity.Company;
import de.votian.services.entity.CostCenter;
import de.votian.services.entity.Courier;
import de.votian.services.entity.Customer;
import de.votian.services.entity.Job;
import de.votian.services.entity.MetaType;
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.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
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 TrackingServiceTest {

    private JobRepository jobRepository;
    private TourRepository tourRepository;
    private AddressRepository addressRepository;
    private CostCenterRepository costCenterRepository;
    private CustomerRepository customerRepository;
    private CompanyRepository companyRepository;
    private CourierRepository courierRepository;
    private VehicleDispositionRepository vehicleDispositionRepository;
    private ServiceZipCodeRepository serviceZipCodeRepository;
    private MetaTypeRepository metaTypeRepository;
    private TrackingService trackingService;

    @BeforeEach
    void setUp() {
        jobRepository = mock(JobRepository.class);
        tourRepository = mock(TourRepository.class);
        addressRepository = mock(AddressRepository.class);
        costCenterRepository = mock(CostCenterRepository.class);
        customerRepository = mock(CustomerRepository.class);
        companyRepository = mock(CompanyRepository.class);
        courierRepository = mock(CourierRepository.class);
        vehicleDispositionRepository = mock(VehicleDispositionRepository.class);
        serviceZipCodeRepository = mock(ServiceZipCodeRepository.class);
        metaTypeRepository = mock(MetaTypeRepository.class);

        trackingService = new TrackingService(
                jobRepository,
                tourRepository,
                addressRepository,
                costCenterRepository,
                customerRepository,
                companyRepository,
                courierRepository,
                mock(CourierVehicleRepository.class),
                vehicleDispositionRepository,
                serviceZipCodeRepository,
                metaTypeRepository
        );

        when(vehicleDispositionRepository.findByJobIdIn(anyList())).thenReturn(List.of());
        when(metaTypeRepository.findByTypeOrderBySort("vehicletype")).thenReturn(List.of(vehicleType(4, "Transporter")));
        when(companyRepository.findAllById(any())).thenReturn(List.of());
    }

    @Test
    void getDashboardBuildsJobAndCourierTrackingWithZipFallback() {
        Job job = job(1L, 1, 7L, 77L, LocalDateTime.of(2026, 3, 28, 9, 0));
        job.setVehicleTypeId(4);

        Tour pickup = tour(1L, 1, 11L, 7L, "Abholung");
        Tour delivery = tour(1L, 2, 12L, 7L, "Ziel");
        Address pickupAddress = address(11L, "20095", "Hamburg");
        Address deliveryAddress = address(12L, "10115", "Berlin");

        CostCenter costCenter = new CostCenter();
        costCenter.setId(7L);
        costCenter.setCustomerId(8L);
        costCenter.setName("Zentrale");

        Customer customer = new Customer();
        customer.setId(8L);
        customer.setCompanyId(9L);

        Company customerCompany = new Company();
        customerCompany.setId(9L);
        customerCompany.setComp("Acme GmbH");

        Courier courier = new Courier();
        courier.setId(77L);
        courier.setCompanyId(10L);
        courier.setSid("CR-77");
        courier.setLocationZipcode("20095");
        courier.setMobilePda("PDA-1");
        courier.setGpsType(9);
        courier.setAvailableTime(LocalDateTime.of(2026, 3, 28, 8, 30));

        Company courierCompany = new Company();
        courierCompany.setId(10L);
        courierCompany.setComp("Kurier Nord");

        when(jobRepository.findForDispositionDay(1L,
                LocalDate.of(2026, 3, 28).atStartOfDay(),
                LocalDate.of(2026, 3, 29).atStartOfDay().minusNanos(1)))
                .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(costCenterRepository.findAllById(any())).thenReturn(List.of(costCenter));
        when(customerRepository.findAllById(any())).thenReturn(List.of(customer));
        when(companyRepository.findAllById(any())).thenReturn(List.of(customerCompany, courierCompany));
        when(courierRepository.findAllById(any())).thenReturn(List.of(courier));
        when(serviceZipCodeRepository.findByZipcodeIn(any())).thenReturn(List.of(
                zipcode("20095", 53.5500d, 10.0000d),
                zipcode("10115", 52.5200d, 13.4050d)
        ));

        TrackingDashboardDto result = trackingService.getDashboard(1L, LocalDate.of(2026, 3, 28), false, null, null);

        assertThat(result.getTotalJobs()).isEqualTo(1);
        assertThat(result.getTrackedJobs()).isEqualTo(1);
        assertThat(result.getTotalCouriers()).isEqualTo(1);
        assertThat(result.getGpsCouriers()).isEqualTo(0);
        assertThat(result.getFallbackCouriers()).isEqualTo(1);
        TrackingJobRowDto row = result.getJobs().getFirst();
        assertThat(row.getCustomerName()).isEqualTo("Acme GmbH");
        assertThat(row.getPickupLatitude()).isEqualTo(53.5500d);
        assertThat(row.getDeliveryLongitude()).isEqualTo(13.4050d);
        assertThat(row.isCourierTracked()).isTrue();
        assertThat(row.isCourierGpsBased()).isFalse();
        assertThat(row.getCourierLocationZipcode()).isEqualTo("20095");
    }

    @Test
    void getDashboardRespectsCompletedFilter() {
        Job openJob = job(1L, 1, 7L, null, LocalDateTime.of(2026, 3, 28, 9, 0));
        Job deliveredJob = job(2L, 8, 7L, null, LocalDateTime.of(2026, 3, 28, 11, 0));

        when(jobRepository.findForDispositionDay(1L,
                LocalDate.of(2026, 3, 28).atStartOfDay(),
                LocalDate.of(2026, 3, 29).atStartOfDay().minusNanos(1)))
                .thenReturn(List.of(openJob, deliveredJob));
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(anyList())).thenReturn(List.of());
        when(addressRepository.findAllById(any())).thenReturn(List.of());
        when(costCenterRepository.findAllById(any())).thenReturn(List.of());
        when(customerRepository.findAllById(any())).thenReturn(List.of());
        when(courierRepository.findAllById(any())).thenReturn(List.of());
        when(serviceZipCodeRepository.findByZipcodeIn(any())).thenReturn(List.of());

        TrackingDashboardDto withoutCompleted = trackingService.getDashboard(1L, LocalDate.of(2026, 3, 28), false, null, null);
        TrackingDashboardDto withCompleted = trackingService.getDashboard(1L, LocalDate.of(2026, 3, 28), true, null, null);

        assertThat(withoutCompleted.getJobs()).extracting(TrackingJobRowDto::getJobId).containsExactly(1L);
        assertThat(withCompleted.getJobs()).extracting(TrackingJobRowDto::getJobId).containsExactly(2L, 1L);
    }

    @Test
    void getDashboardFiltersByCustomerUsingResolvedCostCenter() {
        Job customerOneJob = job(1L, 1, null, null, LocalDateTime.of(2026, 3, 28, 9, 0));
        Job customerTwoJob = job(2L, 1, 9L, null, LocalDateTime.of(2026, 3, 28, 10, 0));

        Tour pickupOne = tour(1L, 1, 11L, 7L, "Abholung");
        Tour deliveryOne = tour(1L, 2, 12L, 7L, "Ziel");

        CostCenter customerOneCostCenter = new CostCenter();
        customerOneCostCenter.setId(7L);
        customerOneCostCenter.setCustomerId(8L);
        customerOneCostCenter.setName("KST 1");

        CostCenter customerTwoCostCenter = new CostCenter();
        customerTwoCostCenter.setId(9L);
        customerTwoCostCenter.setCustomerId(10L);
        customerTwoCostCenter.setName("KST 2");

        when(jobRepository.findForDispositionDay(1L,
                LocalDate.of(2026, 3, 28).atStartOfDay(),
                LocalDate.of(2026, 3, 29).atStartOfDay().minusNanos(1)))
                .thenReturn(List.of(customerOneJob, customerTwoJob));
        when(tourRepository.findByJobIdInOrderByJobIdAscSortAsc(List.of(1L, 2L))).thenReturn(List.of(pickupOne, deliveryOne));
        when(addressRepository.findAllById(any())).thenReturn(List.of());
        when(costCenterRepository.findAllById(any())).thenReturn(List.of(customerOneCostCenter, customerTwoCostCenter));
        when(customerRepository.findAllById(any())).thenReturn(List.of());
        when(courierRepository.findAllById(any())).thenReturn(List.of());
        when(serviceZipCodeRepository.findByZipcodeIn(any())).thenReturn(List.of());

        TrackingDashboardDto result = trackingService.getDashboard(1L, LocalDate.of(2026, 3, 28), false, 8L, null);

        assertThat(result.getJobs()).extracting(TrackingJobRowDto::getJobId).containsExactly(1L);
    }

    private Job job(Long id, Integer status, Long costCenterPayerId, Long courierId, LocalDateTime orderTime) {
        Job job = new Job();
        job.setId(id);
        job.setStatus(status);
        job.setHeadquartersId(1L);
        job.setOrderTime(orderTime);
        job.setCostCenterPayerId(costCenterPayerId);
        job.setCourierId(courierId);
        job.setCourierSid(courierId != null ? "CR-" + courierId : null);
        return job;
    }

    private Tour tour(Long jobId, int sort, Long addressId, Long costCenterId, String comp) {
        Tour tour = new Tour();
        tour.setJobId(jobId);
        tour.setSort(sort);
        tour.setAddressId(addressId);
        tour.setCostCenterId(costCenterId);
        tour.setComp(comp);
        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;
    }

    private MetaType vehicleType(int sort, String value) {
        MetaType metaType = new MetaType();
        metaType.setSort(sort);
        metaType.setValue(value);
        return metaType;
    }
}
