package de.votian.services.service;

import de.votian.services.dto.ImportExecutionResultDto;
import de.votian.services.dto.ImportFileDto;
import de.votian.services.dto.ImportProcessDto;
import de.votian.services.entity.Customer;
import de.votian.services.entity.ServiceRadius;
import de.votian.services.entity.ServiceZipCode;
import de.votian.services.entity.ServiceZipCodeNeighbour;
import de.votian.services.entity.ServiceZone;
import de.votian.services.entity.ServiceZoneMapping;
import de.votian.services.repository.CustomerRepository;
import de.votian.services.repository.ServiceRadiusRepository;
import de.votian.services.repository.ServiceZipCodeRepository;
import de.votian.services.repository.ServiceZipCodeNeighbourRepository;
import de.votian.services.repository.ServiceZoneMappingRepository;
import de.votian.services.repository.ServiceZoneRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.mock.web.MockMultipartFile;

import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;

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

@SuppressWarnings("null")
class ImportWorkspaceServiceTest {

    @TempDir
    Path tempDir;

    private CustomerRepository customerRepository;
    private ServiceZoneRepository serviceZoneRepository;
    private ServiceZoneMappingRepository serviceZoneMappingRepository;
    private ServiceZipCodeRepository serviceZipCodeRepository;
    private ServiceRadiusRepository serviceRadiusRepository;
    private ServiceZipCodeNeighbourRepository serviceZipCodeNeighbourRepository;
    private ImportWorkspaceService importWorkspaceService;

    @BeforeEach
    void setUp() {
        customerRepository = mock(CustomerRepository.class);
        serviceZoneRepository = mock(ServiceZoneRepository.class);
        serviceZoneMappingRepository = mock(ServiceZoneMappingRepository.class);
        serviceZipCodeRepository = mock(ServiceZipCodeRepository.class);
        serviceRadiusRepository = mock(ServiceRadiusRepository.class);
        serviceZipCodeNeighbourRepository = mock(ServiceZipCodeNeighbourRepository.class);
        importWorkspaceService = new ImportWorkspaceService(
                customerRepository,
                serviceZoneRepository,
                serviceZoneMappingRepository,
                serviceZipCodeRepository,
                serviceRadiusRepository,
                serviceZipCodeNeighbourRepository,
                tempDir.resolve("imports").toString()
        );
    }

    @Test
    void storeAndListFilesPerProcess() {
        importWorkspaceService.storeFile("ZONE", new MockMultipartFile(
                "file",
                "zone.csv",
                "text/csv",
                "1;DE;04109;12,50".getBytes()
        ));

        List<ImportFileDto> files = importWorkspaceService.listFiles("ZONE");

        assertThat(files).hasSize(1);
        assertThat(files.getFirst().getFileName()).isEqualTo("zone.csv");
        assertThat(files.getFirst().getSize()).isGreaterThan(0);
    }

    @Test
    void executeZoneImportUpdatesZoneMappingsForCustomer() {
        Customer customer = new Customer();
        customer.setId(7L);
        customer.setHeadquartersId(1L);

        ServiceZone existingZone = new ServiceZone();
        existingZone.setId(11L);
        existingZone.setHeadquartersId(1L);
        existingZone.setCustomerId(7L);
        existingZone.setZoneNo(1);

        ServiceZipCode zipCode = new ServiceZipCode();
        zipCode.setId(101L);
        zipCode.setZipcode("04109");

        when(customerRepository.findById(7L)).thenReturn(Optional.of(customer));
        when(serviceZoneRepository.findByHeadquartersIdAndCustomerIdOrderByZoneNo(1L, 7L)).thenReturn(List.of(existingZone));
        when(serviceZipCodeRepository.findByZipcodeIn(anyCollection())).thenReturn(List.of(zipCode));
        when(serviceZoneRepository.save(any(ServiceZone.class))).thenAnswer(invocation -> invocation.getArgument(0));

        importWorkspaceService.storeFile("ZONE", new MockMultipartFile(
                "file",
                "zone.csv",
                "text/csv",
                ("1;DE;04109;12,50;1,00;0,00\n"
                        + "2;DE;99999;9,99;0;0\n").getBytes()
        ));

        AtomicLong zoneIds = new AtomicLong(20L);
        when(serviceZoneRepository.save(any(ServiceZone.class))).thenAnswer(invocation -> {
            ServiceZone zone = invocation.getArgument(0);
            if (zone.getId() == null) {
                zone.setId(zoneIds.getAndIncrement());
            }
            return zone;
        });

        ImportExecutionResultDto result = importWorkspaceService.execute("ZONE", "zone.csv", 7L, 1L);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.getUpdatedCount()).isEqualTo(1);
        assertThat(result.getCreatedCount()).isEqualTo(2);
        assertThat(result.getIgnoredCount()).isEqualTo(1);
        verify(serviceZoneMappingRepository).deleteByZoneId(11L);
        verify(serviceZoneMappingRepository).saveAll(any());
    }

    @Test
    void executeRadiusImportReplacesImportedAreasOnly() {
        Customer customer = new Customer();
        customer.setId(9L);
        customer.setHeadquartersId(2L);

        ServiceRadius keep = new ServiceRadius();
        keep.setId(1L);
        keep.setRadiusAreaNo(1);
        ServiceRadius replace = new ServiceRadius();
        replace.setId(2L);
        replace.setRadiusAreaNo(2);

        when(customerRepository.findById(9L)).thenReturn(Optional.of(customer));
        when(serviceRadiusRepository.findByHeadquartersIdAndCustomerIdOrderByZipcodeAscCityDistrictAsc(2L, 9L))
                .thenReturn(List.of(keep, replace));

        importWorkspaceService.storeFile("RADIUSZONE", new MockMultipartFile(
                "file",
                "radius.csv",
                "text/csv",
                ("2;01067;Mitte\n"
                        + "2;01069;Suedvorstadt\n").getBytes()
        ));

        ImportExecutionResultDto result = importWorkspaceService.execute("RADIUSZONE", "radius.csv", 9L, 2L);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.getCreatedCount()).isEqualTo(2);
        assertThat(result.getDeletedCount()).isEqualTo(1);
        verify(serviceRadiusRepository).deleteAll(List.of(replace));
        verify(serviceRadiusRepository).saveAll(any());
    }

    @Test
    void executeZoneAgioImportResetsAndUpdatesCustomerMappings() {
        Customer customer = new Customer();
        customer.setId(7L);
        customer.setHeadquartersId(1L);

        ServiceZone zone1 = new ServiceZone();
        zone1.setId(11L);
        ServiceZone zone2 = new ServiceZone();
        zone2.setId(12L);

        ServiceZipCode zipcode1 = new ServiceZipCode();
        zipcode1.setId(101L);
        zipcode1.setZipcode("04109");

        ServiceZoneMapping mapping1 = new ServiceZoneMapping();
        mapping1.setZoneId(11L);
        mapping1.setZipCodeId(101L);
        mapping1.setMappedValue2(new java.math.BigDecimal("1.00"));

        ServiceZoneMapping mapping2 = new ServiceZoneMapping();
        mapping2.setZoneId(12L);
        mapping2.setZipCodeId(101L);
        mapping2.setMappedValue2(new java.math.BigDecimal("2.00"));

        ServiceZoneMapping mapping3 = new ServiceZoneMapping();
        mapping3.setZoneId(11L);
        mapping3.setZipCodeId(999L);
        mapping3.setMappedValue2(new java.math.BigDecimal("3.00"));

        when(customerRepository.findById(7L)).thenReturn(Optional.of(customer));
        when(serviceZoneRepository.findByHeadquartersIdAndCustomerIdOrderByZoneNo(1L, 7L)).thenReturn(List.of(zone1, zone2));
        when(serviceZoneMappingRepository.findByZoneIdIn(List.of(11L, 12L))).thenReturn(List.of(mapping1, mapping2, mapping3));
        when(serviceZipCodeRepository.findByZipcodeIn(anyCollection())).thenReturn(List.of(zipcode1));

        importWorkspaceService.storeFile("ZONEAGIO", new MockMultipartFile(
                "file",
                "zoneagio.csv",
                "text/csv",
                ("04109;4,50\n"
                        + "99999;2,00\n").getBytes()
        ));

        ImportExecutionResultDto result = importWorkspaceService.execute("ZONEAGIO", "zoneagio.csv", 7L, 1L);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.getUpdatedCount()).isEqualTo(2);
        assertThat(result.getIgnoredCount()).isEqualTo(1);
        assertThat(mapping1.getMappedValue2()).isEqualByComparingTo("4.50");
        assertThat(mapping2.getMappedValue2()).isEqualByComparingTo("4.50");
        assertThat(mapping3.getMappedValue2()).isEqualByComparingTo("0.00");
        verify(serviceZoneMappingRepository).saveAll(any());
    }

    @Test
    void executeZipcodeNeighboursImportCreatesMissingRelationsOnly() {
        ServiceZipCode baseZip = new ServiceZipCode();
        baseZip.setId(101L);
        baseZip.setZipcode("04109");

        ServiceZipCode neighbourExisting = new ServiceZipCode();
        neighbourExisting.setId(102L);
        neighbourExisting.setZipcode("04103");

        ServiceZipCode neighbourNew = new ServiceZipCode();
        neighbourNew.setId(103L);
        neighbourNew.setZipcode("04105");

        ServiceZipCodeNeighbour existingRelation = new ServiceZipCodeNeighbour();
        existingRelation.setHeadquartersId(1L);
        existingRelation.setZipCodeId(101L);
        existingRelation.setNeighbourZipCodeId(102L);
        existingRelation.setSortOrder(1);

        when(serviceZipCodeRepository.findByZipcodeIn(anyCollection())).thenReturn(List.of(baseZip, neighbourExisting, neighbourNew));
        when(serviceZipCodeNeighbourRepository.findByHeadquartersIdAndZipCodeIdIn(org.mockito.ArgumentMatchers.eq(1L), anyCollection()))
                .thenReturn(List.of(existingRelation));

        importWorkspaceService.storeFile("ZIPCODE_NEIGHBOURS", new MockMultipartFile(
                "file",
                "neighbours.csv",
                "text/csv",
                "04109;04103;04105;99999\n".getBytes()
        ));

        ImportExecutionResultDto result = importWorkspaceService.execute("ZIPCODE_NEIGHBOURS", "neighbours.csv", null, 1L);

        assertThat(result.isSuccess()).isTrue();
        assertThat(result.getCreatedCount()).isEqualTo(1);
        assertThat(result.getIgnoredCount()).isEqualTo(1);
        verify(serviceZipCodeNeighbourRepository).saveAll(any());
    }

    @Test
    void getProcessesExposesNativeAndArchivedLegacyClassification() {
        List<ImportProcessDto> processes = importWorkspaceService.getProcesses();

        ImportProcessDto zoneAgio = processes.stream()
                .filter(process -> "ZONEAGIO".equals(process.getKey()))
                .findFirst()
                .orElseThrow();
        ImportProcessDto customerJobs = processes.stream()
                .filter(process -> "CUSTOMER_JOBS".equals(process.getKey()))
                .findFirst()
                .orElseThrow();

        assertThat(zoneAgio.isNativeExecution()).isTrue();
        assertThat(zoneAgio.getStatus()).isEqualTo("Nativ");
        assertThat(zoneAgio.getPriority()).isEqualTo("Hoch");
        assertThat(zoneAgio.getLegacySources()).containsExactly("import_ZONEAGIO.php");

        assertThat(customerJobs.isNativeExecution()).isFalse();
        assertThat(customerJobs.getStatus()).isEqualTo("Archiviert");
        assertThat(customerJobs.getLegacySources()).contains("import_BAUHAUS.php", "import_TOOM.php");
    }
}
