package de.cannamanage.service;

import de.cannamanage.domain.constants.ComplianceConstants;
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.entity.User;
import de.cannamanage.domain.enums.MemberStatus;
import de.cannamanage.domain.enums.UserRole;
import de.cannamanage.service.dto.QuotaStatus;
import de.cannamanage.service.dto.portal.PortalDashboard;
import de.cannamanage.service.dto.portal.PortalDistributionHistory;
import de.cannamanage.service.dto.portal.PortalProfile;
import de.cannamanage.service.dto.portal.PortalQuota;
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 de.cannamanage.service.repository.UserRepository;
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 org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

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

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

@ExtendWith(MockitoExtension.class)
class PortalServiceTest {

    @Mock private MemberRepository memberRepository;
    @Mock private DistributionRepository distributionRepository;
    @Mock private ComplianceService complianceService;
    @Mock private BatchRepository batchRepository;
    @Mock private StrainRepository strainRepository;
    @Mock private UserRepository userRepository;

    @InjectMocks private PortalService portalService;

    private final UUID tenantId = UUID.randomUUID();
    private final UUID memberId = UUID.randomUUID();
    private Member testMember;

    @BeforeEach
    void setUp() {
        testMember = new Member();
        testMember.setFirstName("Max");
        testMember.setLastName("Mustermann");
        testMember.setMembershipNumber("CM-001");
        testMember.setMembershipDate(LocalDate.of(2025, 1, 15));
        testMember.setStatus(MemberStatus.ACTIVE);
        testMember.setEmail("max@example.com");
        testMember.setUnder21(false);
    }

    @Test
    void getDashboard_returnsCorrectQuotaData() {
        when(memberRepository.findById(memberId)).thenReturn(Optional.of(testMember));
        when(complianceService.getQuotaStatus(memberId)).thenReturn(
                new QuotaStatus(new BigDecimal("50.0"), new BigDecimal("12.5"),
                        new BigDecimal("37.5"), false, 2026, 6));
        when(distributionRepository.sumQuantityByMemberAndDay(eq(memberId), any(), any()))
                .thenReturn(new BigDecimal("5.0"));
        when(distributionRepository.findTop5ByMemberIdAndTenantIdOrderByDistributedAtDesc(memberId, tenantId))
                .thenReturn(List.of());

        PortalDashboard dashboard = portalService.getDashboard(tenantId, memberId);

        assertThat(dashboard.memberName()).isEqualTo("Max Mustermann");
        assertThat(dashboard.membershipNumber()).isEqualTo("CM-001");
        assertThat(dashboard.monthlyQuotaUsed()).isEqualByComparingTo("12.5");
        assertThat(dashboard.monthlyQuotaRemaining()).isEqualByComparingTo("37.5");
        assertThat(dashboard.dailyQuotaUsed()).isEqualByComparingTo("5.0");
        assertThat(dashboard.dailyQuotaRemaining()).isEqualByComparingTo("20.0");
        assertThat(dashboard.recentDistributions()).isEmpty();
    }

    @Test
    void getProfile_returnsOwnMemberData() {
        when(memberRepository.findById(memberId)).thenReturn(Optional.of(testMember));

        PortalProfile profile = portalService.getProfile(tenantId, memberId);

        assertThat(profile.firstName()).isEqualTo("Max");
        assertThat(profile.lastName()).isEqualTo("Mustermann");
        assertThat(profile.membershipNumber()).isEqualTo("CM-001");
        assertThat(profile.membershipDate()).isEqualTo(LocalDate.of(2025, 1, 15));
        assertThat(profile.status()).isEqualTo(MemberStatus.ACTIVE);
        assertThat(profile.email()).isEqualTo("max@example.com");
    }

    @Test
    void getQuota_returnsDetailedQuotaStatus() {
        when(memberRepository.findById(memberId)).thenReturn(Optional.of(testMember));
        when(complianceService.getQuotaStatus(memberId)).thenReturn(
                new QuotaStatus(new BigDecimal("50.0"), new BigDecimal("20.0"),
                        new BigDecimal("30.0"), false, 2026, 6));
        when(distributionRepository.sumQuantityByMemberAndDay(eq(memberId), any(), any()))
                .thenReturn(new BigDecimal("10.0"));

        PortalQuota quota = portalService.getQuota(tenantId, memberId);

        assertThat(quota.year()).isEqualTo(2026);
        assertThat(quota.month()).isEqualTo(6);
        assertThat(quota.dailyUsed()).isEqualByComparingTo("10.0");
        assertThat(quota.dailyLimit()).isEqualByComparingTo(ComplianceConstants.ADULT_DAILY_LIMIT_GRAMS);
        assertThat(quota.monthlyUsed()).isEqualByComparingTo("20.0");
        assertThat(quota.monthlyLimit()).isEqualByComparingTo("50.0");
        assertThat(quota.isUnder21()).isFalse();
    }

    @Test
    void getDistributionHistory_returnsPaginatedResults() {
        UUID batchId = UUID.randomUUID();
        UUID staffId = UUID.randomUUID();
        UUID strainId = UUID.randomUUID();

        Distribution dist = new Distribution();
        dist.setDistributedAt(Instant.now());
        dist.setBatchId(batchId);
        dist.setQuantityGrams(new BigDecimal("3.5"));
        dist.setRecordedBy(staffId);

        Batch batch = new Batch();
        batch.setStrainId(strainId);
        Strain strain = new Strain();
        strain.setName("Blue Dream");
        User staff = new User();
        staff.setEmail("staff@club.de");

        Pageable pageable = PageRequest.of(0, 20);
        when(distributionRepository.findByMemberIdAndTenantIdOrderByDistributedAtDesc(memberId, tenantId, pageable))
                .thenReturn(new PageImpl<>(List.of(dist), pageable, 1));
        when(batchRepository.findById(batchId)).thenReturn(Optional.of(batch));
        when(strainRepository.findById(strainId)).thenReturn(Optional.of(strain));
        when(userRepository.findById(staffId)).thenReturn(Optional.of(staff));

        PortalDistributionHistory history = portalService.getDistributionHistory(tenantId, memberId, pageable);

        assertThat(history.totalElements()).isEqualTo(1);
        assertThat(history.page()).isEqualTo(0);
        assertThat(history.distributions()).hasSize(1);
        assertThat(history.distributions().getFirst().strainName()).isEqualTo("Blue Dream");
        assertThat(history.distributions().getFirst().grams()).isEqualByComparingTo("3.5");
        assertThat(history.distributions().getFirst().staffName()).isEqualTo("staff@club.de");
    }

    @Test
    void getDashboard_memberOnlySeesOwnData() {
        // A different memberId — service uses the passed memberId, not some other lookup
        UUID otherMemberId = UUID.randomUUID();
        Member otherMember = new Member();
        otherMember.setFirstName("Anna");
        otherMember.setLastName("Schmidt");
        otherMember.setMembershipNumber("CM-002");
        otherMember.setMembershipDate(LocalDate.of(2025, 3, 1));
        otherMember.setStatus(MemberStatus.ACTIVE);
        otherMember.setEmail("anna@example.com");
        otherMember.setUnder21(true);

        when(memberRepository.findById(otherMemberId)).thenReturn(Optional.of(otherMember));
        when(complianceService.getQuotaStatus(otherMemberId)).thenReturn(
                new QuotaStatus(new BigDecimal("30.0"), new BigDecimal("5.0"),
                        new BigDecimal("25.0"), true, 2026, 6));
        when(distributionRepository.sumQuantityByMemberAndDay(eq(otherMemberId), any(), any()))
                .thenReturn(BigDecimal.ZERO);
        when(distributionRepository.findTop5ByMemberIdAndTenantIdOrderByDistributedAtDesc(otherMemberId, tenantId))
                .thenReturn(List.of());

        PortalDashboard dashboard = portalService.getDashboard(tenantId, otherMemberId);

        // Verifies it returns ANNA's data, not MAX's — proving member-scoped isolation
        assertThat(dashboard.memberName()).isEqualTo("Anna Schmidt");
        assertThat(dashboard.membershipNumber()).isEqualTo("CM-002");
    }
}
