package de.cannamanage.service;

import de.cannamanage.domain.entity.Club;
import de.cannamanage.domain.enums.BatchStatus;
import de.cannamanage.domain.enums.ClubStatus;
import de.cannamanage.domain.enums.MemberStatus;
import de.cannamanage.service.repository.BatchRepository;
import de.cannamanage.service.repository.ClubRepository;
import de.cannamanage.service.repository.DistributionRepository;
import de.cannamanage.service.repository.MemberRepository;
import de.cannamanage.service.repository.StaffAccountRepository;
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.web.server.ResponseStatusException;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
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.*;

@ExtendWith(MockitoExtension.class)
class ClubServiceTest {

    @Mock
    private ClubRepository clubRepository;
    @Mock
    private MemberRepository memberRepository;
    @Mock
    private StaffAccountRepository staffAccountRepository;
    @Mock
    private DistributionRepository distributionRepository;
    @Mock
    private BatchRepository batchRepository;

    @InjectMocks
    private ClubService clubService;

    private UUID tenantId;
    private Club club;

    @BeforeEach
    void setUp() {
        tenantId = UUID.randomUUID();
        club = new Club();
        club.setId(UUID.randomUUID());
        club.setTenantId(tenantId);
        club.setName("Test Club");
        club.setLicenseNumber("LIC-001");
        club.setMaxPreventionOfficers(2);
        club.setStatus(ClubStatus.ACTIVE);
    }

    @Test
    void getClubByTenantId_found() {
        when(clubRepository.findByTenantId(tenantId)).thenReturn(Optional.of(club));

        Club result = clubService.getClubByTenantId(tenantId);

        assertThat(result).isEqualTo(club);
        verify(clubRepository).findByTenantId(tenantId);
    }

    @Test
    void getClubByTenantId_notFound_throws404() {
        when(clubRepository.findByTenantId(tenantId)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> clubService.getClubByTenantId(tenantId))
                .isInstanceOf(ResponseStatusException.class)
                .hasMessageContaining("Club not found for tenant");
    }

    @Test
    void updateClub_success() {
        when(clubRepository.findByTenantId(tenantId)).thenReturn(Optional.of(club));
        when(clubRepository.save(any(Club.class))).thenReturn(club);

        Club result = clubService.updateClub(
                tenantId, "Updated Club", "REG-123",
                "info@club.de", "+49123456",
                "Mainstreet 1", "Berlin", "10115", "Berlin",
                LocalDate.of(2024, 1, 15), 3, ".*@club\\.de"
        );

        assertThat(result.getName()).isEqualTo("Updated Club");
        assertThat(result.getRegistrationNumber()).isEqualTo("REG-123");
        assertThat(result.getContactEmail()).isEqualTo("info@club.de");
        assertThat(result.getMaxPreventionOfficers()).isEqualTo(3);
        assertThat(result.getAllowedEmailPattern()).isEqualTo(".*@club\\.de");
        verify(clubRepository).save(club);
    }

    @Test
    void updateClub_invalidRegex_throwsBadRequest() {
        when(clubRepository.findByTenantId(tenantId)).thenReturn(Optional.of(club));

        assertThatThrownBy(() -> clubService.updateClub(
                tenantId, "Club", null, null, null,
                null, null, null, null, null, null, "[invalid"
        ))
                .isInstanceOf(ResponseStatusException.class)
                .hasMessageContaining("Invalid regex pattern");
    }

    @Test
    void updateClub_nullPattern_accepted() {
        when(clubRepository.findByTenantId(tenantId)).thenReturn(Optional.of(club));
        when(clubRepository.save(any(Club.class))).thenReturn(club);

        Club result = clubService.updateClub(
                tenantId, "Club", null, null, null,
                null, null, null, null, null, null, null
        );

        assertThat(result).isNotNull();
        verify(clubRepository).save(club);
    }

    @Test
    void updateClub_blankPattern_accepted() {
        when(clubRepository.findByTenantId(tenantId)).thenReturn(Optional.of(club));
        when(clubRepository.save(any(Club.class))).thenReturn(club);

        Club result = clubService.updateClub(
                tenantId, "Club", null, null, null,
                null, null, null, null, null, null, "   "
        );

        assertThat(result).isNotNull();
        verify(clubRepository).save(club);
    }

    @Test
    void getClubStats_returnsAggregatedStats() {
        when(memberRepository.countByTenantId(tenantId)).thenReturn(50L);
        when(memberRepository.countByTenantIdAndStatus(tenantId, MemberStatus.ACTIVE)).thenReturn(42L);
        when(staffAccountRepository.countByTenantId(tenantId)).thenReturn(5L);
        when(staffAccountRepository.countByTenantIdAndActiveTrue(tenantId)).thenReturn(4L);
        when(distributionRepository.countByTenantIdAndDistributedAtAfter(eq(tenantId), any(Instant.class)))
                .thenReturn(120L);
        when(distributionRepository.sumGramsByTenantIdAndDistributedAtAfter(eq(tenantId), any(Instant.class)))
                .thenReturn(new BigDecimal("1500.50"));
        when(batchRepository.countByTenantIdAndStatus(tenantId, BatchStatus.AVAILABLE)).thenReturn(8L);
        when(staffAccountRepository.countByTenantIdAndPreventionOfficerTrue(tenantId)).thenReturn(2L);

        ClubService.ClubStats stats = clubService.getClubStats(tenantId);

        assertThat(stats.totalMembers()).isEqualTo(50L);
        assertThat(stats.activeMembers()).isEqualTo(42L);
        assertThat(stats.totalStaff()).isEqualTo(5L);
        assertThat(stats.activeStaff()).isEqualTo(4L);
        assertThat(stats.totalDistributionsThisMonth()).isEqualTo(120L);
        assertThat(stats.totalGramsDistributedThisMonth()).isEqualByComparingTo("1500.50");
        assertThat(stats.activeBatches()).isEqualTo(8L);
        assertThat(stats.preventionOfficerCount()).isEqualTo(2L);
    }

    @Test
    void getClubStats_nullGrams_returnsZero() {
        when(memberRepository.countByTenantId(tenantId)).thenReturn(0L);
        when(memberRepository.countByTenantIdAndStatus(tenantId, MemberStatus.ACTIVE)).thenReturn(0L);
        when(staffAccountRepository.countByTenantId(tenantId)).thenReturn(0L);
        when(staffAccountRepository.countByTenantIdAndActiveTrue(tenantId)).thenReturn(0L);
        when(distributionRepository.countByTenantIdAndDistributedAtAfter(eq(tenantId), any(Instant.class)))
                .thenReturn(0L);
        when(distributionRepository.sumGramsByTenantIdAndDistributedAtAfter(eq(tenantId), any(Instant.class)))
                .thenReturn(null);
        when(batchRepository.countByTenantIdAndStatus(tenantId, BatchStatus.AVAILABLE)).thenReturn(0L);
        when(staffAccountRepository.countByTenantIdAndPreventionOfficerTrue(tenantId)).thenReturn(0L);

        ClubService.ClubStats stats = clubService.getClubStats(tenantId);

        assertThat(stats.totalGramsDistributedThisMonth()).isEqualByComparingTo(BigDecimal.ZERO);
    }
}
