package de.votian.services.service;

import de.votian.services.dto.JobTourServiceRowDto;
import jakarta.persistence.EntityManager;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

class JobTourServiceReadServiceTest {

    private static final String PLACEHOLDER_FILTER_SQL =
            "AND NOT (trs.trs_srvt_name = 'z' AND trs.trs_price = 0) ";
    private static final String SELECT_SQL =
            "SELECT trs.csc_id, csc.csc_name, trs.tr_sort, trs.trs_srv_name, trs.trs_srvt_name, trs.trs_price, trs.trs_discount " +
                    "FROM tourservice trs " +
                    "LEFT JOIN costcenter csc ON trs.csc_id = csc.csc_id " +
                    "WHERE trs.jb_id = :jobId " +
                    PLACEHOLDER_FILTER_SQL +
                    "ORDER BY trs.csc_id, trs.tr_sort, trs.trs_srv_name, trs.trs_srvt_name";
    private static final String SELECT_BY_JOB_IDS_SQL =
            "SELECT trs.jb_id, trs.csc_id, csc.csc_name, trs.tr_sort, trs.trs_srv_name, trs.trs_srvt_name, trs.trs_price, trs.trs_discount " +
                    "FROM tourservice trs " +
                    "LEFT JOIN costcenter csc ON trs.csc_id = csc.csc_id " +
                    "WHERE trs.jb_id IN (:jobIds) " +
                    PLACEHOLDER_FILTER_SQL +
                    "ORDER BY trs.jb_id, trs.csc_id, trs.tr_sort, trs.trs_srv_name, trs.trs_srvt_name";
    private static final String SELECT_BUSINESS_VOLUME_SQL =
            "SELECT trs.jb_id, trs.csc_id, SUM(trs.trs_price * ((100 - trs.trs_discount) / 100)) " +
                    "FROM tourservice trs " +
                    "WHERE trs.jb_id IN (:jobIds) " +
                    PLACEHOLDER_FILTER_SQL +
                    "GROUP BY trs.jb_id, trs.csc_id " +
                    "ORDER BY trs.jb_id, trs.csc_id";

    @Test
    void findByJobIdMapsPersistedTourserviceRowsAndBusinessVolume() {
        EntityManager entityManager = entityManager(List.of(
                new Object[]{77L, "Payer Nord", 0, "Fixpreis", "", new BigDecimal("120.00"), new BigDecimal("0.00")},
                new Object[]{77L, "Payer Nord", 2, "Zone A", "Express", new BigDecimal("25.50"), new BigDecimal("10.00")}
        ), List.of(), List.of());
        JobTourServiceReadService service = new JobTourServiceReadService(entityManager);

        List<JobTourServiceRowDto> result = service.findByJobId(5L);

        assertThat(result).hasSize(2);
        assertThat(result)
                .extracting(
                        JobTourServiceRowDto::getCostCenterId,
                        JobTourServiceRowDto::getCostCenterName,
                        JobTourServiceRowDto::getTourSort,
                        JobTourServiceRowDto::getServiceName,
                        JobTourServiceRowDto::getServiceTypeName,
                        JobTourServiceRowDto::getPrice,
                        JobTourServiceRowDto::getDiscount,
                        JobTourServiceRowDto::getBusinessVolume
                )
                .containsExactly(
                        org.assertj.core.groups.Tuple.tuple(
                                77L,
                                "Payer Nord",
                                0,
                                "Fixpreis",
                                "",
                                new BigDecimal("120.00"),
                                new BigDecimal("0.00"),
                                new BigDecimal("120.00")
                        ),
                        org.assertj.core.groups.Tuple.tuple(
                                77L,
                                "Payer Nord",
                                2,
                                "Zone A",
                                "Express",
                                new BigDecimal("25.50"),
                                new BigDecimal("10.00"),
                                new BigDecimal("22.95")
                        )
                );
    }

    @Test
    void findBusinessVolumeByJobIdsAggregatesByJobAndCostCenter() {
        EntityManager entityManager = entityManager(
                List.of(),
                List.of(),
                List.of(
                        new Object[]{5L, 11L, new BigDecimal("42.50")},
                        new Object[]{5L, 0L, new BigDecimal("55.00")},
                        new Object[]{6L, 12L, new BigDecimal("18.00")}
                )
        );
        JobTourServiceReadService service = new JobTourServiceReadService(entityManager);

        java.util.Map<Long, java.util.Map<Long, BigDecimal>> result = service.findBusinessVolumeByJobIds(List.of(5L, 6L));

        assertThat(result).containsOnlyKeys(5L, 6L);
        assertThat(result.get(5L)).containsEntry(11L, new BigDecimal("42.50")).containsEntry(0L, new BigDecimal("55.00"));
        assertThat(result.get(6L)).containsEntry(12L, new BigDecimal("18.00"));
    }

    @Test
    void findRowsByJobIdsGroupsPersistedRowsByJobId() {
        EntityManager entityManager = entityManager(
                List.of(),
                List.of(
                        new Object[]{5L, 11L, "Payer Nord", 0, "Fixpreis", "", new BigDecimal("120.00"), new BigDecimal("0.00")},
                        new Object[]{5L, 11L, "Payer Nord", 2, "Zone A", "Express", new BigDecimal("25.50"), new BigDecimal("10.00")},
                        new Object[]{6L, 0L, "", 0, "Servicepreis", "", new BigDecimal("9.99"), new BigDecimal("0.00")}
                ),
                List.of()
        );
        JobTourServiceReadService service = new JobTourServiceReadService(entityManager);

        java.util.Map<Long, List<JobTourServiceRowDto>> result = service.findRowsByJobIds(List.of(5L, 6L));

        assertThat(result).containsOnlyKeys(5L, 6L);
        assertThat(result.get(5L))
                .extracting(JobTourServiceRowDto::getServiceName, JobTourServiceRowDto::getBusinessVolume)
                .containsExactly(
                        org.assertj.core.groups.Tuple.tuple("Fixpreis", new BigDecimal("120.00")),
                        org.assertj.core.groups.Tuple.tuple("Zone A", new BigDecimal("22.95"))
                );
        assertThat(result.get(6L)).singleElement()
                .extracting(JobTourServiceRowDto::getServiceName, JobTourServiceRowDto::getBusinessVolume)
                .containsExactly("Servicepreis", new BigDecimal("9.99"));
    }

    @Test
    void findByJobIdSkipsLegacyPlaceholderRows() {
        EntityManager entityManager = entityManager(List.<Object[]>of(
                new Object[]{77L, "Payer Nord", 0, "Fixpreis", "", new BigDecimal("120.00"), new BigDecimal("0.00")}
        ), List.of(), List.of());
        JobTourServiceReadService service = new JobTourServiceReadService(entityManager);

        List<JobTourServiceRowDto> result = service.findByJobId(5L);

        assertThat(result).singleElement()
                .extracting(JobTourServiceRowDto::getServiceName, JobTourServiceRowDto::getBusinessVolume)
                .containsExactly("Fixpreis", new BigDecimal("120.00"));
    }

    @Test
    void findRowsByJobIdsSkipsLegacyPlaceholderRows() {
        EntityManager entityManager = entityManager(
                List.of(),
                List.of(
                        new Object[]{5L, 11L, "Payer Nord", 0, "Fixpreis", "", new BigDecimal("120.00"), new BigDecimal("0.00")},
                        new Object[]{6L, 0L, "", 0, "Servicepreis", "", new BigDecimal("9.99"), new BigDecimal("0.00")}
                ),
                List.of()
        );
        JobTourServiceReadService service = new JobTourServiceReadService(entityManager);

        java.util.Map<Long, List<JobTourServiceRowDto>> result = service.findRowsByJobIds(List.of(5L, 6L));

        assertThat(result).containsOnlyKeys(5L, 6L);
        assertThat(result.get(5L)).singleElement()
                .extracting(JobTourServiceRowDto::getServiceName, JobTourServiceRowDto::getBusinessVolume)
                .containsExactly("Fixpreis", new BigDecimal("120.00"));
        assertThat(result.get(6L)).singleElement()
                .extracting(JobTourServiceRowDto::getServiceName, JobTourServiceRowDto::getBusinessVolume)
                .containsExactly("Servicepreis", new BigDecimal("9.99"));
    }

    @Test
    void findByJobIdNormalizesLegacyLineBreakMarkupInServiceLabels() {
        EntityManager entityManager = entityManager(List.<Object[]>of(
                new Object[]{77L, "Payer Nord", 0, "Weitere<br>Adressen", "PKW<br/>Kombi", new BigDecimal("25.50"), new BigDecimal("0.00")}
        ), List.of(), List.of());
        JobTourServiceReadService service = new JobTourServiceReadService(entityManager);

        List<JobTourServiceRowDto> result = service.findByJobId(5L);

        assertThat(result).singleElement()
                .extracting(JobTourServiceRowDto::getServiceName, JobTourServiceRowDto::getServiceTypeName)
                .containsExactly("Weitere Adressen", "PKW Kombi");
    }

    private EntityManager entityManager(List<Object[]> detailRows,
                                        List<Object[]> groupedRows,
                                        List<Object[]> businessVolumeRows) {
        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(detailRows);
                        }
                        if (SELECT_BY_JOB_IDS_SQL.equals(sql)) {
                            return query(groupedRows);
                        }
                        if (SELECT_BUSINESS_VOLUME_SQL.equals(sql)) {
                            return query(businessVolumeRows);
                        }
                    }
                    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) {
        return Proxy.newProxyInstance(
                jakarta.persistence.Query.class.getClassLoader(),
                new Class[]{jakarta.persistence.Query.class},
                (proxy, method, args) -> {
                    if ("setParameter".equals(method.getName())) {
                        return proxy;
                    }
                    if ("getResultList".equals(method.getName())) {
                        return rows;
                    }
                    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());
                }
        );
    }
}
