package de.votian.services.service;

import de.votian.services.dto.JobPricingResultDto;
import de.votian.services.dto.JobTourServiceDraftRowDto;
import de.votian.services.entity.Address;
import de.votian.services.entity.CostCenter;
import de.votian.services.entity.CostCenterAddress;
import de.votian.services.entity.Customer;
import de.votian.services.repository.AddressRepository;
import de.votian.services.repository.CostCenterAddressRepository;
import de.votian.services.repository.CostCenterRepository;
import de.votian.services.repository.ServiceZoneMappingRepository;
import de.votian.services.repository.ServiceZoneRepository;
import de.votian.services.repository.ServiceZipCodeRepository;
import de.votian.services.entity.ServiceZone;
import de.votian.services.entity.ServiceZoneMapping;
import de.votian.services.entity.ServiceZipCode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

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

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

class LegacyXmlIntegrationServiceTest {

    private LegacyXmlCustomerAuthService authService;
    private CostCenterRepository costCenterRepository;
    private CostCenterAddressRepository costCenterAddressRepository;
    private AddressRepository addressRepository;
    private ServiceZoneRepository serviceZoneRepository;
    private ServiceZoneMappingRepository serviceZoneMappingRepository;
    private ServiceZipCodeRepository serviceZipCodeRepository;
    private JobPricingService jobPricingService;
    private LegacyXmlIntegrationService service;

    @BeforeEach
    void setUp() {
        authService = mock(LegacyXmlCustomerAuthService.class);
        costCenterRepository = mock(CostCenterRepository.class);
        costCenterAddressRepository = mock(CostCenterAddressRepository.class);
        addressRepository = mock(AddressRepository.class);
        serviceZoneRepository = mock(ServiceZoneRepository.class);
        serviceZoneMappingRepository = mock(ServiceZoneMappingRepository.class);
        serviceZipCodeRepository = mock(ServiceZipCodeRepository.class);
        jobPricingService = mock(JobPricingService.class);
        service = new LegacyXmlIntegrationService(
                authService,
                costCenterRepository,
                costCenterAddressRepository,
                addressRepository,
                serviceZoneRepository,
                serviceZoneMappingRepository,
                serviceZipCodeRepository,
                jobPricingService
        );
    }

    @Test
    void costcenterRequestBuildsLegacyXmlFromCostcenterAndAddressData() {
        Customer customer = customer(7L, 1L);
        CostCenter root = costCenter(10L, 7L, "Root", null);
        CostCenter child = costCenter(11L, 7L, "Filiale", 10L);
        CostCenterAddress childAddress = new CostCenterAddress();
        childAddress.setCostCenterId(11L);
        childAddress.setAddressId(20L);
        childAddress.setAddressTypeId(4);
        childAddress.setHsno("12a");
        childAddress.setFloor("2");
        childAddress.setPerson("Max Mustermann");
        childAddress.setPhone("0301234");
        childAddress.setEmail("test@example.org");
        childAddress.setRemark("Rampe");
        childAddress.setComp("Firma");

        Address address = new Address();
        address.setId(20L);
        address.setStreet("Teststrasse");
        address.setZipcode("10115");
        address.setCity("Berlin");
        address.setCountry("DE");

        when(authService.authenticate(any())).thenReturn(authContext(customer, null, root));
        when(costCenterRepository.findByCustomerId(7L)).thenReturn(List.of(root, child));
        when(costCenterAddressRepository.findByCostCenterIdAndAddressTypeId(10L, 4)).thenReturn(Optional.empty());
        when(costCenterAddressRepository.findByCostCenterIdAndAddressTypeId(11L, 4)).thenReturn(Optional.of(childAddress));
        when(addressRepository.findById(20L)).thenReturn(Optional.of(address));

        String response = service.handleCostcenterRequest("""
                <xml><costcenter><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id></auth></costcenter></xml>
                """);

        assertThat(response).contains("<state>OK</state>");
        assertThat(response).contains("<costcenter_name><![CDATA[Filiale]]></costcenter_name>");
        assertThat(response).contains("<costcenter_pre_no><![CDATA[10]]></costcenter_pre_no>");
        assertThat(response).contains("<street><![CDATA[Teststrasse]]></street>");
    }

    @Test
    void zoneRequestResolvesCustomerZoneByZipcode() {
        Customer customer = customer(7L, 1L);
        CostCenter root = costCenter(10L, 7L, "Root", null);
        ServiceZipCode zipcode = new ServiceZipCode();
        zipcode.setId(100L);
        zipcode.setZipcode("04109");
        ServiceZone zone = new ServiceZone();
        zone.setId(200L);
        zone.setZoneNo(5);
        zone.setName("Leipzig");
        ServiceZoneMapping mapping = new ServiceZoneMapping();
        mapping.setZoneId(200L);
        mapping.setZipCodeId(100L);

        when(authService.authenticate(any())).thenReturn(authContext(customer, null, root));
        when(serviceZipCodeRepository.findByZipcode("04109")).thenReturn(Optional.of(zipcode));
        when(serviceZoneRepository.findByHeadquartersIdAndCustomerIdOrderByZoneNo(1L, 7L)).thenReturn(List.of(zone));
        when(serviceZoneMappingRepository.findByZoneIdIn(anyCollection())).thenReturn(List.of(mapping));

        String response = service.handleZoneRequest("""
                <xml><zone><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id></auth><eco_zipcode>04109</eco_zipcode></zone></xml>
                """);

        assertThat(response).contains("<state>OK</state>");
        assertThat(response).contains("<zone_no>5</zone_no>");
        assertThat(response).contains("<zone_name><![CDATA[Leipzig]]></zone_name>");
    }

    @Test
    void priceRequestUsesJobPricingServiceAndReturnsBasicAndRouteComponent() {
        Customer customer = customer(7L, 1L);
        CostCenter root = costCenter(10L, 7L, "Root", null);
        CostCenter selected = costCenter(11L, 7L, "Filiale", 10L);

        JobTourServiceDraftRowDto basic = new JobTourServiceDraftRowDto();
        basic.setServiceName("Grundpreis");
        basic.setPrice(new BigDecimal("10.00"));
        JobTourServiceDraftRowDto route = new JobTourServiceDraftRowDto();
        route.setServiceName("PLZ 10115 nach PLZ 04109");
        route.setPrice(new BigDecimal("7.50"));
        JobPricingResultDto pricing = new JobPricingResultDto();
        pricing.setTourServices(List.of(basic, route));

        when(authService.authenticate(any())).thenReturn(authContext(customer, selected, root));
        when(jobPricingService.calculateDraft(any(), anyList(), anyList())).thenReturn(pricing);

        String response = service.handlePriceRequest("""
                <xml><costs><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id><costcenter_name>Filiale</costcenter_name></auth><vehicle_type_id>3</vehicle_type_id><zipcode_from>10115</zipcode_from><zipcode_to>04109</zipcode_to><day>28</day><month>3</month><year>2026</year></costs></xml>
                """);

        ArgumentCaptor<de.votian.services.entity.Job> jobCaptor = ArgumentCaptor.forClass(de.votian.services.entity.Job.class);
        verify(jobPricingService).calculateDraft(jobCaptor.capture(), anyList(), anyList());

        assertThat(jobCaptor.getValue().getCostCenterPayerId()).isEqualTo(11L);
        assertThat(jobCaptor.getValue().getVehicleTypeId()).isEqualTo(3);
        assertThat(jobCaptor.getValue().getOrderTime()).isEqualTo(LocalDateTime.of(2026, 3, 28, 0, 0));
        assertThat(response).contains("<price_value_1>10.00</price_value_1>");
        assertThat(response).contains("<price_value_2>7.50</price_value_2>");
        assertThat(response).contains("<price_value_sum>17.50</price_value_sum>");
    }

    private Customer customer(Long id, Long hqId) {
        Customer customer = new Customer();
        customer.setId(id);
        customer.setHeadquartersId(hqId);
        customer.setEid("AG-100");
        customer.setCostCenterPayerId(10L);
        return customer;
    }

    private CostCenter costCenter(Long id, Long customerId, String name, Long parentId) {
        CostCenter costCenter = new CostCenter();
        costCenter.setId(id);
        costCenter.setCustomerId(customerId);
        costCenter.setName(name);
        costCenter.setParentId(parentId);
        return costCenter;
    }

    private LegacyXmlCustomerAuthService.AuthContext authContext(Customer customer,
                                                                 CostCenter requestedCostCenter,
                                                                 CostCenter defaultCostCenter) {
        return new LegacyXmlCustomerAuthService.AuthContext(customer, null, null, requestedCostCenter, defaultCostCenter);
    }
}
