package de.votian.services.service;

import de.votian.services.config.ParameterKeys;
import de.votian.services.entity.Parameter;
import de.votian.services.repository.EmployeeRepository;
import de.votian.services.repository.ParameterRepository;
import de.votian.services.repository.UserRepository;
import jakarta.persistence.EntityManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class JobTourServiceSyncServiceTest {

    private static final String SELECT_SQL =
            "SELECT tr_sort, trs_srv_name, trs_price FROM tourservice WHERE jb_id = :jobId";
    private static final String UPDATE_SQL =
            "UPDATE tourservice SET trs_price = :newPrice " +
                    "WHERE jb_id = :jobId AND tr_sort = :trSort AND trs_srv_name = :serviceName AND trs_price = :oldPrice";

    private ParameterRepository parameterRepository;
    private ParameterService parameterService;

    @BeforeEach
    void setUp() {
        parameterRepository = mock(ParameterRepository.class);
        when(parameterRepository.findByKeyAndHqAndEmployee(anyString(), anyLong(), anyLong())).thenReturn(Optional.empty());
        parameterService = new ParameterService(
                parameterRepository,
                mock(EmployeeRepository.class),
                mock(UserRepository.class)
        );
    }

    @Test
    void syncMarkupAdjustedPricesUpdatesMarkupSensitiveTourserviceRows() {
        List<Map<String, Object>> updates = new java.util.ArrayList<>();
        EntityManager entityManager = entityManager(
                java.util.Collections.singletonList(new Object[]{0, "Fixpreis", new BigDecimal("110.00")}),
                updates
        );
        JobTourServiceSyncService service = new JobTourServiceSyncService(parameterService, entityManager);
        service.syncMarkupAdjustedPrices(5L, 1L, new BigDecimal("10.00"), new BigDecimal("20.00"));

        org.assertj.core.api.Assertions.assertThat(updates).hasSize(1);
        Map<String, Object> update = updates.get(0);
        org.assertj.core.api.Assertions.assertThat(update.get("newPrice")).isEqualTo(new BigDecimal("120.00"));
        org.assertj.core.api.Assertions.assertThat(update.get("jobId")).isEqualTo(5L);
        org.assertj.core.api.Assertions.assertThat(update.get("trSort")).isEqualTo(0);
        org.assertj.core.api.Assertions.assertThat(update.get("serviceName")).isEqualTo("Fixpreis");
        org.assertj.core.api.Assertions.assertThat(update.get("oldPrice")).isEqualTo(new BigDecimal("110.00"));
    }

    @Test
    void syncMarkupAdjustedPricesSkipsServicePriceAndSuppressedBasicPrice() {
        Parameter parameter = new Parameter();
        parameter.setKey(ParameterKeys.MASK_NO_MARKUP_ON_VEHICLE_PRICE);
        parameter.setHeadquartersId(1L);
        parameter.setEmployeeId(0L);
        parameter.setValue("1");
        when(parameterRepository.findByKeyAndHqAndEmployee(ParameterKeys.MASK_NO_MARKUP_ON_VEHICLE_PRICE, 1L, 0L))
                .thenReturn(Optional.of(parameter));

        List<Map<String, Object>> updates = new java.util.ArrayList<>();
        EntityManager entityManager = entityManager(
                java.util.Arrays.asList(
                        new Object[]{0, "Servicepreis", new BigDecimal("10.00")},
                        new Object[]{1, "Grundpreis", new BigDecimal("110.00")}
                ),
                updates
        );
        JobTourServiceSyncService service = new JobTourServiceSyncService(parameterService, entityManager);
        service.syncMarkupAdjustedPrices(5L, 1L, new BigDecimal("10.00"), new BigDecimal("20.00"));

        org.assertj.core.api.Assertions.assertThat(updates).isEmpty();
    }

    private EntityManager entityManager(List<Object[]> rows, List<Map<String, Object>> updates) {
        return (EntityManager) Proxy.newProxyInstance(
                EntityManager.class.getClassLoader(),
                new Class[]{EntityManager.class},
                (proxy, method, args) -> {
                    if ("createNativeQuery".equals(method.getName())) {
                        String sql = (String) args[0];
                        if (SELECT_SQL.equals(sql)) {
                            return query(rows, null);
                        }
                        if (UPDATE_SQL.equals(sql)) {
                            Map<String, Object> parameters = new LinkedHashMap<>();
                            updates.add(parameters);
                            return query(List.of(), parameters);
                        }
                    }
                    if ("toString".equals(method.getName())) {
                        return "FakeEntityManager";
                    }
                    if ("hashCode".equals(method.getName())) {
                        return System.identityHashCode(proxy);
                    }
                    if ("equals".equals(method.getName())) {
                        return proxy == args[0];
                    }
                    throw new UnsupportedOperationException("Unexpected EntityManager method: " + method.getName());
                }
        );
    }

    private Object query(List<Object[]> rows, Map<String, Object> parameters) {
        return Proxy.newProxyInstance(
                jakarta.persistence.Query.class.getClassLoader(),
                new Class[]{jakarta.persistence.Query.class},
                (proxy, method, args) -> {
                    if ("setParameter".equals(method.getName())) {
                        if (parameters != null) {
                            parameters.put((String) args[0], args[1]);
                        }
                        return proxy;
                    }
                    if ("getResultList".equals(method.getName())) {
                        return rows;
                    }
                    if ("executeUpdate".equals(method.getName())) {
                        return 1;
                    }
                    if ("toString".equals(method.getName())) {
                        return "FakeQuery";
                    }
                    if ("hashCode".equals(method.getName())) {
                        return System.identityHashCode(proxy);
                    }
                    if ("equals".equals(method.getName())) {
                        return proxy == args[0];
                    }
                    throw new UnsupportedOperationException("Unexpected Query method: " + method.getName());
                }
        );
    }
}
