package de.cannamanage.service;

import de.cannamanage.domain.entity.Batch;
import de.cannamanage.domain.entity.Distribution;
import de.cannamanage.domain.entity.Member;
import de.cannamanage.domain.entity.Strain;
import de.cannamanage.domain.enums.BatchStatus;
import de.cannamanage.domain.enums.MemberStatus;
import de.cannamanage.service.model.report.MemberListReport;
import de.cannamanage.service.model.report.MonthlyReport;
import de.cannamanage.service.model.report.RecallReport;
import de.cannamanage.service.repository.BatchRepository;
import de.cannamanage.service.repository.DistributionRepository;
import de.cannamanage.service.repository.MemberRepository;
import de.cannamanage.service.repository.StrainRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class ReportServiceTest {

    @Mock
    private DistributionRepository distributionRepository;
    @Mock
    private MemberRepository memberRepository;
    @Mock
    private BatchRepository batchRepository;
    @Mock
    private StrainRepository strainRepository;

    @InjectMocks
    private ReportService reportService;

    private static final UUID TENANT_ID = UUID.randomUUID();
    private static final UUID MEMBER_1 = UUID.randomUUID();
    private static final UUID MEMBER_2 = UUID.randomUUID();
    private static final UUID BATCH_ID = UUID.randomUUID();
    private static final UUID STRAIN_ID = UUID.randomUUID();

    @Test
    void testGenerateMonthlyReport_withDistributions() {
        YearMonth month = YearMonth.of(2026, 3);

        Distribution d1 = createDistribution(MEMBER_1, BATCH_ID, new BigDecimal("3.50"),
                Instant.parse("2026-03-10T14:00:00Z"));
        Distribution d2 = createDistribution(MEMBER_2, BATCH_ID, new BigDecimal("5.00"),
                Instant.parse("2026-03-15T10:00:00Z"));
        Distribution d3 = createDistribution(MEMBER_1, BATCH_ID, new BigDecimal("2.50"),
                Instant.parse("2026-03-15T16:00:00Z"));

        when(distributionRepository.findByTenantIdAndDistributedAtBetween(eq(TENANT_ID), any(), any()))
                .thenReturn(List.of(d1, d2, d3));

        Batch batch = createBatch(BATCH_ID, STRAIN_ID, "BATCH-001");
        when(batchRepository.findById(BATCH_ID)).thenReturn(Optional.of(batch));

        Strain strain = new Strain();
        strain.setId(STRAIN_ID);
        strain.setName("White Widow");
        when(strainRepository.findById(STRAIN_ID)).thenReturn(Optional.of(strain));

        MonthlyReport report = reportService.generateMonthlyReport(TENANT_ID, month);

        assertThat(report.getMonth()).isEqualTo(month);
        assertThat(report.getTotalDistributions()).isEqualTo(3);
        assertThat(report.getTotalGrams()).isEqualByComparingTo("11.00");
        assertThat(report.getUniqueMembers()).isEqualTo(2);
        assertThat(report.getAveragePerMember()).isEqualByComparingTo("5.50");
        assertThat(report.getTopStrains()).hasSize(1);
        assertThat(report.getTopStrains().get(0).getName()).isEqualTo("White Widow");
        assertThat(report.getDailyBreakdown()).hasSize(31); // March has 31 days
    }

    @Test
    void testGenerateMonthlyReport_emptyMonth() {
        YearMonth month = YearMonth.of(2026, 1);

        when(distributionRepository.findByTenantIdAndDistributedAtBetween(eq(TENANT_ID), any(), any()))
                .thenReturn(List.of());

        MonthlyReport report = reportService.generateMonthlyReport(TENANT_ID, month);

        assertThat(report.getTotalDistributions()).isZero();
        assertThat(report.getTotalGrams()).isEqualByComparingTo("0");
        assertThat(report.getUniqueMembers()).isZero();
        assertThat(report.getAveragePerMember()).isEqualByComparingTo("0");
        assertThat(report.getTopStrains()).isEmpty();
        assertThat(report.getDailyBreakdown()).hasSize(31);
    }

    @Test
    void testGenerateMemberListReport_allMembers() {
        Member m1 = createMember(MEMBER_1, "Max", "Mustermann", "M-001", MemberStatus.ACTIVE);
        Member m2 = createMember(MEMBER_2, "Anna", "Muster", "M-002", MemberStatus.SUSPENDED);

        when(memberRepository.findByTenantId(TENANT_ID)).thenReturn(List.of(m1, m2));
        when(distributionRepository.countByTenantIdAndMemberId(TENANT_ID, MEMBER_1)).thenReturn(5L);
        when(distributionRepository.countByTenantIdAndMemberId(TENANT_ID, MEMBER_2)).thenReturn(0L);
        when(distributionRepository.findLatestByTenantIdAndMemberId(TENANT_ID, MEMBER_1)).thenReturn(null);
        when(distributionRepository.findLatestByTenantIdAndMemberId(TENANT_ID, MEMBER_2)).thenReturn(null);

        MemberListReport report = reportService.generateMemberListReport(TENANT_ID, null);

        assertThat(report.getGeneratedAt()).isNotNull();
        assertThat(report.getMembers()).hasSize(2);
        assertThat(report.getMembers().get(0).getFirstName()).isEqualTo("Max");
        assertThat(report.getMembers().get(0).getTotalDistributions()).isEqualTo(5);
        assertThat(report.getMembers().get(1).getStatus()).isEqualTo(MemberStatus.SUSPENDED);
    }

    @Test
    void testGenerateMemberListReport_filteredByStatus() {
        Member m1 = createMember(MEMBER_1, "Max", "Mustermann", "M-001", MemberStatus.ACTIVE);

        when(memberRepository.findByTenantIdAndStatus(TENANT_ID, MemberStatus.ACTIVE))
                .thenReturn(List.of(m1));
        when(distributionRepository.countByTenantIdAndMemberId(TENANT_ID, MEMBER_1)).thenReturn(3L);
        when(distributionRepository.findLatestByTenantIdAndMemberId(TENANT_ID, MEMBER_1)).thenReturn(null);

        MemberListReport report = reportService.generateMemberListReport(TENANT_ID, MemberStatus.ACTIVE);

        assertThat(report.getMembers()).hasSize(1);
        assertThat(report.getMembers().get(0).getStatus()).isEqualTo(MemberStatus.ACTIVE);
    }

    @Test
    void testGenerateRecallReport_success() {
        Batch batch = createBatch(BATCH_ID, STRAIN_ID, "BATCH-RECALL-01");
        batch.setHarvestDate(LocalDate.of(2026, 2, 15));

        Strain strain = new Strain();
        strain.setId(STRAIN_ID);
        strain.setName("Amnesia Haze");

        Distribution d1 = createDistribution(MEMBER_1, BATCH_ID, new BigDecimal("5.00"),
                Instant.parse("2026-03-01T10:00:00Z"));
        Distribution d2 = createDistribution(MEMBER_2, BATCH_ID, new BigDecimal("3.00"),
                Instant.parse("2026-03-02T14:00:00Z"));

        when(batchRepository.findById(BATCH_ID)).thenReturn(Optional.of(batch));
        when(strainRepository.findById(STRAIN_ID)).thenReturn(Optional.of(strain));
        when(distributionRepository.findByTenantIdAndBatchId(TENANT_ID, BATCH_ID))
                .thenReturn(List.of(d1, d2));

        Member member1 = createMember(MEMBER_1, "Max", "Mustermann", "M-001", MemberStatus.ACTIVE);
        Member member2 = createMember(MEMBER_2, "Anna", "Muster", "M-002", MemberStatus.ACTIVE);
        when(memberRepository.findById(MEMBER_1)).thenReturn(Optional.of(member1));
        when(memberRepository.findById(MEMBER_2)).thenReturn(Optional.of(member2));

        RecallReport report = reportService.generateRecallReport(TENANT_ID, BATCH_ID);

        assertThat(report.getBatchId()).isEqualTo(BATCH_ID);
        assertThat(report.getStrainName()).isEqualTo("Amnesia Haze");
        assertThat(report.getBatchNumber()).isEqualTo("BATCH-RECALL-01");
        assertThat(report.getReceivedDate()).isEqualTo(LocalDate.of(2026, 2, 15));
        assertThat(report.getTotalGramsDistributed()).isEqualByComparingTo("8.00");
        assertThat(report.getAffectedMembers()).hasSize(2);
        assertThat(report.getAffectedMembers().get(0).getFirstName()).isEqualTo("Max");
        assertThat(report.getAffectedMembers().get(1).getGrams()).isEqualByComparingTo("3.00");
    }

    @Test
    void testGenerateRecallReport_batchNotFound() {
        when(batchRepository.findById(BATCH_ID)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> reportService.generateRecallReport(TENANT_ID, BATCH_ID))
                .isInstanceOf(de.cannamanage.service.exception.BatchNotFoundException.class);
    }

    // --- Helper methods ---

    private Distribution createDistribution(UUID memberId, UUID batchId, BigDecimal grams, Instant at) {
        Distribution d = new Distribution();
        d.setId(UUID.randomUUID());
        d.setTenantId(TENANT_ID);
        d.setMemberId(memberId);
        d.setBatchId(batchId);
        d.setQuantityGrams(grams);
        d.setDistributedAt(at);
        return d;
    }

    private Member createMember(UUID id, String first, String last, String number, MemberStatus status) {
        Member m = new Member();
        m.setId(id);
        m.setTenantId(TENANT_ID);
        m.setFirstName(first);
        m.setLastName(last);
        m.setMembershipNumber(number);
        m.setStatus(status);
        m.setMembershipDate(LocalDate.of(2025, 6, 1));
        return m;
    }

    private Batch createBatch(UUID id, UUID strainId, String code) {
        Batch b = new Batch();
        b.setId(id);
        b.setTenantId(TENANT_ID);
        b.setStrainId(strainId);
        b.setBatchCode(code);
        b.setQuantityGrams(new BigDecimal("50.00"));
        b.setStatus(BatchStatus.AVAILABLE);
        return b;
    }
}
