package de.votian.services.service;

import de.votian.services.entity.CostCenter;
import de.votian.services.entity.Customer;
import de.votian.services.entity.GenericDataContainer;
import de.votian.services.entity.Job;
import de.votian.services.entity.MetaType;
import de.votian.services.entity.Tour;
import de.votian.services.repository.AddressRepository;
import de.votian.services.repository.CostCenterRepository;
import de.votian.services.repository.GenericDataContainerRepository;
import de.votian.services.repository.MetaTypeRepository;
import de.votian.services.repository.TourRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@SuppressWarnings("null")
class LegacyXmlOrderDataIntegrationServiceTest {

    private LegacyXmlCustomerAuthService authService;
    private GenericDataContainerRepository genericDataContainerRepository;
    private CostCenterRepository costCenterRepository;
    private MetaTypeRepository metaTypeRepository;
    private AddressRepository addressRepository;
    private TourRepository tourRepository;
    private JobService jobService;
    private JobTourServiceWriteService jobTourServiceWriteService;
    private JdbcTemplate jdbcTemplate;
    private LegacyXmlOrderDataIntegrationService service;

    @BeforeEach
    void setUp() {
        authService = mock(LegacyXmlCustomerAuthService.class);
        genericDataContainerRepository = mock(GenericDataContainerRepository.class);
        costCenterRepository = mock(CostCenterRepository.class);
        metaTypeRepository = mock(MetaTypeRepository.class);
        addressRepository = mock(AddressRepository.class);
        tourRepository = mock(TourRepository.class);
        jobService = mock(JobService.class);
        jobTourServiceWriteService = mock(JobTourServiceWriteService.class);
        jdbcTemplate = mock(JdbcTemplate.class);
        service = new LegacyXmlOrderDataIntegrationService(
                authService,
                genericDataContainerRepository,
                costCenterRepository,
                metaTypeRepository,
                addressRepository,
                tourRepository,
                jobService,
                jobTourServiceWriteService,
                jdbcTemplate
        );
    }

    @Test
    void orderDataGetBuildsLegacyFieldsForAuthenticatedCustomer() {
        when(authService.authenticate(any())).thenReturn(authContext(7L));

        CostCenter payer = new CostCenter();
        payer.setId(10L);
        payer.setCustomerId(7L);
        when(costCenterRepository.findById(10L)).thenReturn(Optional.of(payer));

        when(jdbcTemplate.queryForList(contains("SELECT j.* FROM job"), eq(1001L)))
                .thenReturn(List.of(Map.of(
                        "jb_id", 1001L,
                        "csc_id_payer", 10L,
                        "jb_status", 1,
                        "vht_id", 3,
                        "jb_totalprice", new BigDecimal("17.50"),
                        "jb_ordertime", Timestamp.valueOf(LocalDateTime.of(2026, 3, 28, 8, 30))
                )));
        when(jdbcTemplate.queryForList(contains("FROM tour t LEFT JOIN address a"), eq(1001L)))
                .thenReturn(List.of(
                        Map.of("tr_comp", "Pickup GmbH", "tr_phone", "030111"),
                        Map.of("tr_comp", "Delivery GmbH", "tr_phone", "030222")
                ));

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(3);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(3, "vehicletype")).thenReturn(Optional.of(vehicleType));

        String response = service.handleOrderDataRequest("""
                <xml><order_data><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id></auth><order_numbers>1001</order_numbers><list_fields>jb_status|vht_id|jb_ordertime|tr_comp|tr_phone</list_fields></order_data></xml>
                """);

        assertThat(response).contains("<state>OK</state>");
        assertThat(response).contains("<order_no>1001</order_no>");
        assertThat(response).contains("<order_op_no>1001</order_op_no>");
        assertThat(response).contains("<field_name><![CDATA[jb_status]]></field_name>");
        assertThat(response).contains("<field_value><![CDATA[1]]></field_value>");
        assertThat(response).contains("<field_name><![CDATA[vht_id]]></field_name>");
        assertThat(response).contains("<field_value><![CDATA[PKW]]></field_value>");
        assertThat(response).contains("<field_value><![CDATA[2026-03-28 08:30:00]]></field_value>");
        assertThat(response).contains("<field_name><![CDATA[tr_comp]]></field_name>");
        assertThat(response).contains("<no>1</no>");
        assertThat(response).contains("<field_value><![CDATA[Pickup GmbH]]></field_value>");
        assertThat(response).contains("<no>2</no>");
        assertThat(response).contains("<field_value><![CDATA[Delivery GmbH]]></field_value>");
    }

    @Test
    void orderDataGetUsesOperativeMappingForNonOperativeOrderNumbers() {
        when(authService.authenticate(any())).thenReturn(authContext(7L));

        CostCenter payer = new CostCenter();
        payer.setId(10L);
        payer.setCustomerId(7L);
        when(costCenterRepository.findById(10L)).thenReturn(Optional.of(payer));

        GenericDataContainer mapping = new GenericDataContainer();
        mapping.setContent("2002|1");
        when(genericDataContainerRepository.findByTypeAndIdAndField("jb", 1001L, "is_operative"))
                .thenReturn(Optional.of(mapping));

        when(jdbcTemplate.queryForList(contains("SELECT j.* FROM job"), eq(1001L)))
                .thenReturn(List.of(Map.of("jb_id", 1001L, "csc_id_payer", 10L)));
        when(jdbcTemplate.queryForList(contains("SELECT j.* FROM job"), eq(2002L)))
                .thenReturn(List.of(Map.of("jb_id", 2002L, "csc_id_payer", 10L, "jb_status", 9)));

        String response = service.handleOrderDataRequest("""
                <xml><order_data><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id></auth><order_numbers>1001</order_numbers><is_operative>0</is_operative><list_fields>jb_status</list_fields></order_data></xml>
                """);

        assertThat(response).contains("<order_no>1001</order_no>");
        assertThat(response).contains("<order_op_no>2002</order_op_no>");
        assertThat(response).contains("<field_value><![CDATA[9]]></field_value>");
    }

    @Test
    void orderDataGetReturnsNotAuthenticatedMarkerForForeignJobs() {
        when(authService.authenticate(any())).thenReturn(authContext(7L));

        CostCenter foreignPayer = new CostCenter();
        foreignPayer.setId(11L);
        foreignPayer.setCustomerId(8L);
        when(costCenterRepository.findById(11L)).thenReturn(Optional.of(foreignPayer));

        when(jdbcTemplate.queryForList(contains("SELECT j.* FROM job"), eq(1001L)))
                .thenReturn(List.of(Map.of("jb_id", 1001L, "csc_id_payer", 11L, "jb_status", 1)));

        String response = service.handleOrderDataRequest("""
                <xml><order_data><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id></auth><order_numbers>1001</order_numbers><list_fields>jb_status</list_fields></order_data></xml>
                """);

        assertThat(response).contains("<field_name><![CDATA[ERR]]></field_name>");
        assertThat(response).contains("<field_value><![CDATA[NOT_AUTHENTICATED]]></field_value>");
        verify(genericDataContainerRepository, never()).findByTypeAndIdAndField("jb", 1001L, "is_operative");
    }

    @Test
    void orderDataSetTakenAssignsCarrierAndUpdatesStatus() {
        when(authService.authenticate(any())).thenReturn(authContext(7L));

        CostCenter payer = new CostCenter();
        payer.setId(10L);
        payer.setCustomerId(7L);
        when(costCenterRepository.findById(10L)).thenReturn(Optional.of(payer));

        when(jdbcTemplate.queryForList(contains("SELECT j.* FROM job"), eq(1001L)))
                .thenReturn(List.of(row(
                        "jb_id", 1001L,
                        "csc_id_payer", 10L,
                        "jb_status", 0
                )));
        when(jdbcTemplate.queryForList(contains("FROM couriervehicle"), eq(77L)))
                .thenReturn(List.of(row(
                        "crvh_id", 77L,
                        "cr_id", 44L,
                        "crvh_sid", "CAR-1"
                )));
        when(jdbcTemplate.queryForList(contains("FROM courier WHERE"), eq(44L)))
                .thenReturn(List.of(row(
                        "cr_id", 44L,
                        "cr_eid", "CR-44"
                )));

        String response = service.handleOrderDataRequest("""
                <xml><order_data><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id></auth><operation>SET</operation><order_numbers>1001</order_numbers><list_jb_status>1</list_jb_status><list_cr_id>44</list_cr_id><list_crvh_id>77</list_crvh_id></order_data></xml>
                """);

        assertThat(response).contains("<state>OK</state>");
        assertThat(response).contains("<order_no>1001</order_no>");
        assertThat(response).contains("<op_code>0</op_code>");
        verify(jdbcTemplate).update(
                contains("UPDATE job SET cr_id = ?, cr_id_order = ?, cr_sid = ?"),
                eq(44L),
                eq(44L),
                eq("CAR-1"),
                eq(1001L)
        );
        verify(jdbcTemplate).update(
                contains("UPDATE job SET jb_status = ?, jb_globaljob = ?, jb_autoranking = ?"),
                eq(1),
                eq(0),
                eq(0),
                eq(1001L)
        );
    }

    @Test
    void orderDataSetFinishedUpdatesFinishTime() {
        when(authService.authenticate(any())).thenReturn(authContext(7L));

        CostCenter payer = new CostCenter();
        payer.setId(10L);
        payer.setCustomerId(7L);
        when(costCenterRepository.findById(10L)).thenReturn(Optional.of(payer));

        when(jdbcTemplate.queryForList(contains("SELECT j.* FROM job"), eq(1001L)))
                .thenReturn(List.of(row(
                        "jb_id", 1001L,
                        "csc_id_payer", 10L,
                        "jb_status", 1
                )));

        String response = service.handleOrderDataRequest("""
                <xml><order_data><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id></auth><operation>SET</operation><order_numbers>1001</order_numbers><list_jb_status>2</list_jb_status></order_data></xml>
                """);

        assertThat(response).contains("<op_code>0</op_code>");
        verify(jdbcTemplate).update(
                contains("UPDATE job SET jb_status = ?, jb_finishtime = ?"),
                eq(2),
                any(Timestamp.class),
                eq(1001L)
        );
    }

    @Test
    void orderDataSetExportedWritesInvoiceNumber() {
        when(authService.authenticate(any())).thenReturn(authContext(7L));

        CostCenter payer = new CostCenter();
        payer.setId(10L);
        payer.setCustomerId(7L);
        when(costCenterRepository.findById(10L)).thenReturn(Optional.of(payer));

        when(jdbcTemplate.queryForList(contains("SELECT j.* FROM job"), eq(1001L)))
                .thenReturn(List.of(row(
                        "jb_id", 1001L,
                        "csc_id_payer", 10L,
                        "jb_status", 2,
                        "jb_storno", "0",
                        "jb_export_time", null
                )));
        when(jdbcTemplate.queryForList(contains("FROM invoice"), eq(1001L))).thenReturn(List.of());

        String response = service.handleOrderDataRequest("""
                <xml><order_data><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id></auth><operation>SET</operation><order_numbers>1001</order_numbers><list_jb_status>3</list_jb_status><list_inv_no>RE-42</list_inv_no></order_data></xml>
                """);

        assertThat(response).contains("<op_code>0</op_code>");
        verify(jdbcTemplate).update(
                contains("UPDATE job SET jb_export_time = ?"),
                any(Timestamp.class),
                eq(1001L)
        );
        verify(jdbcTemplate).update(
                contains("INSERT INTO invoice"),
                eq(1001L),
                eq("RE-42"),
                any(Timestamp.class)
        );
    }

    @Test
    void orderDataSetReturnsOrderScopeErrorForForeignJobs() {
        when(authService.authenticate(any())).thenReturn(authContext(7L));

        CostCenter foreignPayer = new CostCenter();
        foreignPayer.setId(11L);
        foreignPayer.setCustomerId(8L);
        when(costCenterRepository.findById(11L)).thenReturn(Optional.of(foreignPayer));

        when(jdbcTemplate.queryForList(contains("SELECT j.* FROM job"), eq(1001L)))
                .thenReturn(List.of(row(
                        "jb_id", 1001L,
                        "csc_id_payer", 11L,
                        "jb_status", 1
                )));

        String response = service.handleOrderDataRequest("""
                <xml><order_data><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id></auth><operation>SET</operation><order_numbers>1001</order_numbers><list_jb_status>2</list_jb_status></order_data></xml>
                """);

        assertThat(response).contains("<op_code>102</op_code>");
        verify(jdbcTemplate, never()).update(contains("UPDATE job SET jb_status = ?, jb_finishtime = ?"), any(), any(), any());
    }

    @Test
    void orderDataSetAppliesSelectiveJobAndStationUpdates() {
        when(authService.authenticate(any())).thenReturn(authContext(7L));

        CostCenter payer = new CostCenter();
        payer.setId(10L);
        payer.setCustomerId(7L);
        when(costCenterRepository.findById(10L)).thenReturn(Optional.of(payer));
        when(jdbcTemplate.queryForList(contains("SELECT j.* FROM job"), eq(1001L)))
                .thenReturn(List.of(row(
                        "jb_id", 1001L,
                        "csc_id_payer", 10L,
                        "jb_status", 1
                )));
        Tour tour = new Tour();
        tour.setId(31L);
        tour.setJobId(1001L);
        tour.setSort(1);
        when(tourRepository.findByJobIdAndSort(1001L, 1)).thenReturn(Optional.of(tour));
        when(jobService.saveTour(any(Tour.class))).thenAnswer(invocation -> invocation.getArgument(0));
        when(jobService.getTours(1001L)).thenReturn(List.of(tour));
        when(jobService.findById(1001L)).thenReturn(Optional.of(new Job()));

        String response = service.handleOrderDataRequest("""
                <xml><order_data><auth><customer>AG-100</customer><account>x</account><password>y</password><session_id>z</session_id></auth><operation>SET</operation><order_numbers>1001</order_numbers><list_jb_status>2</list_jb_status><job><jb_tourname>Neu</jb_tourname></job><stations><station><tr_sort>1</tr_sort><tr_comp>Neuer Name</tr_comp></station></stations></order_data></xml>
                """);

        assertThat(response).contains("<state>OK</state>");
        assertThat(response).contains("<op_code>0</op_code>");
        verify(jobService, atLeastOnce()).updateJob(eq(1001L), any(Job.class));
        verify(jobService).saveTour(any(Tour.class));
    }

    private LegacyXmlCustomerAuthService.AuthContext authContext(Long customerId) {
        Customer customer = new Customer();
        customer.setId(customerId);
        customer.setEid("AG-100");
        return new LegacyXmlCustomerAuthService.AuthContext(customer, null, null, null, null);
    }

    private Map<String, Object> row(Object... values) {
        Map<String, Object> row = new HashMap<>();
        for (int i = 0; i < values.length; i += 2) {
            row.put((String) values[i], values[i + 1]);
        }
        return row;
    }
}
