package de.cannamanage.service;

import de.cannamanage.domain.entity.Club;
import de.cannamanage.domain.entity.InviteToken;
import de.cannamanage.domain.entity.StaffAccount;
import de.cannamanage.domain.entity.User;
import de.cannamanage.domain.enums.StaffPermission;
import de.cannamanage.domain.enums.UserRole;
import de.cannamanage.service.repository.ClubRepository;
import de.cannamanage.service.repository.InviteTokenRepository;
import de.cannamanage.service.repository.StaffAccountRepository;
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.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.server.ResponseStatusException;

import java.time.Instant;
import java.util.EnumSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class StaffServiceTest {

    @Mock private UserRepository userRepository;
    @Mock private StaffAccountRepository staffAccountRepository;
    @Mock private InviteTokenRepository inviteTokenRepository;
    @Mock private ClubRepository clubRepository;
    @Mock private EmailService emailService;
    @Mock private TokenRevocationService tokenRevocationService;

    @InjectMocks
    private StaffService staffService;

    private UUID tenantId;
    private Club club;

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

    @Test
    void createStaff_success_createsUserAndStaffAndSendsEmail() {
        // Arrange
        String email = "staff@example.com";
        String displayName = "Max Mustermann";
        Set<StaffPermission> permissions = EnumSet.of(
                StaffPermission.RECORD_DISTRIBUTION, StaffPermission.VIEW_MEMBER_LIST);

        when(userRepository.existsByEmailAndTenantId(email, tenantId)).thenReturn(false);
        when(clubRepository.findById(tenantId)).thenReturn(Optional.of(club));
        when(userRepository.save(any(User.class))).thenAnswer(inv -> {
            User u = inv.getArgument(0);
            u.setId(UUID.randomUUID());
            return u;
        });
        when(staffAccountRepository.save(any(StaffAccount.class))).thenAnswer(inv -> {
            StaffAccount s = inv.getArgument(0);
            s.setId(UUID.randomUUID());
            return s;
        });
        when(inviteTokenRepository.save(any(InviteToken.class))).thenAnswer(inv -> inv.getArgument(0));

        // Act
        StaffAccount result = staffService.createStaff(tenantId, email, displayName, permissions, null);

        // Assert
        assertThat(result).isNotNull();
        assertThat(result.getDisplayName()).isEqualTo(displayName);
        assertThat(result.getGrantedPermissions()).containsExactlyInAnyOrderElementsOf(permissions);

        // Verify user was created inactive with STAFF role
        ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
        verify(userRepository).save(userCaptor.capture());
        User savedUser = userCaptor.getValue();
        assertThat(savedUser.getEmail()).isEqualTo(email);
        assertThat(savedUser.getRole()).isEqualTo(UserRole.ROLE_STAFF);
        assertThat(savedUser.isActive()).isFalse();

        // Verify invite token was created
        ArgumentCaptor<InviteToken> tokenCaptor = ArgumentCaptor.forClass(InviteToken.class);
        verify(inviteTokenRepository).save(tokenCaptor.capture());
        InviteToken savedToken = tokenCaptor.getValue();
        assertThat(savedToken.getToken()).isNotBlank();
        assertThat(savedToken.getExpiresAt()).isAfter(Instant.now());

        // Verify email was sent
        verify(emailService).sendInviteEmail(eq(email), eq(displayName), eq("Test Club"), anyString());
    }

    @Test
    void createStaff_withTemplate_resolvesPermissions() {
        String email = "lager@example.com";
        when(userRepository.existsByEmailAndTenantId(email, tenantId)).thenReturn(false);
        when(clubRepository.findById(tenantId)).thenReturn(Optional.of(club));
        when(userRepository.save(any(User.class))).thenAnswer(inv -> {
            User u = inv.getArgument(0);
            u.setId(UUID.randomUUID());
            return u;
        });
        when(staffAccountRepository.save(any(StaffAccount.class))).thenAnswer(inv -> {
            StaffAccount s = inv.getArgument(0);
            s.setId(UUID.randomUUID());
            return s;
        });
        when(inviteTokenRepository.save(any(InviteToken.class))).thenAnswer(inv -> inv.getArgument(0));

        StaffAccount result = staffService.createStaff(tenantId, email, "Lager Person", null, "lager");

        assertThat(result.getGrantedPermissions()).containsExactlyInAnyOrder(
                StaffPermission.VIEW_STOCK, StaffPermission.RECORD_STOCK_IN);
    }

    @Test
    void createStaff_duplicateEmail_throwsConflict() {
        when(userRepository.existsByEmailAndTenantId("dup@example.com", tenantId)).thenReturn(true);

        assertThatThrownBy(() -> staffService.createStaff(
                tenantId, "dup@example.com", "Dup User", EnumSet.of(StaffPermission.VIEW_STOCK), null))
                .isInstanceOf(ResponseStatusException.class)
                .hasMessageContaining("Email already in use");
    }

    @Test
    void createStaff_emailPatternViolation_throwsBadRequest() {
        club.setAllowedEmailPattern(".*@myclub\\.de$");
        when(userRepository.existsByEmailAndTenantId("user@other.com", tenantId)).thenReturn(false);
        when(clubRepository.findById(tenantId)).thenReturn(Optional.of(club));

        assertThatThrownBy(() -> staffService.createStaff(
                tenantId, "user@other.com", "User", EnumSet.of(StaffPermission.VIEW_STOCK), null))
                .isInstanceOf(ResponseStatusException.class)
                .hasMessageContaining("allowed email pattern");
    }

    @Test
    void updateStaff_permissionChange_revokesTokens() {
        UUID staffId = UUID.randomUUID();
        UUID userId = UUID.randomUUID();
        StaffAccount staff = new StaffAccount();
        staff.setId(staffId);
        staff.setTenantId(tenantId);
        staff.setUserId(userId);
        staff.setDisplayName("Old Name");
        staff.setGrantedPermissions(EnumSet.of(StaffPermission.VIEW_STOCK));
        staff.setActive(true);

        when(staffAccountRepository.findById(staffId)).thenReturn(Optional.of(staff));
        when(staffAccountRepository.save(any(StaffAccount.class))).thenAnswer(inv -> inv.getArgument(0));

        Set<StaffPermission> newPerms = EnumSet.of(StaffPermission.VIEW_STOCK, StaffPermission.RECORD_STOCK_IN);
        staffService.updateStaff(tenantId, staffId, "New Name", newPerms, null, null);

        verify(tokenRevocationService).revokeAllForUser(userId);
    }

    @Test
    void deactivateStaff_deactivatesUserAndRevokesTokens() {
        UUID staffId = UUID.randomUUID();
        UUID userId = UUID.randomUUID();
        StaffAccount staff = new StaffAccount();
        staff.setId(staffId);
        staff.setTenantId(tenantId);
        staff.setUserId(userId);
        staff.setActive(true);

        User user = new User();
        user.setId(userId);
        user.setActive(true);

        when(staffAccountRepository.findById(staffId)).thenReturn(Optional.of(staff));
        when(userRepository.findById(userId)).thenReturn(Optional.of(user));
        when(staffAccountRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
        when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        staffService.deactivateStaff(tenantId, staffId);

        assertThat(staff.isActive()).isFalse();
        assertThat(user.isActive()).isFalse();
        verify(tokenRevocationService).revokeAllForUser(userId);
    }

    @Test
    void getStaff_wrongTenant_throwsNotFound() {
        UUID staffId = UUID.randomUUID();
        StaffAccount staff = new StaffAccount();
        staff.setId(staffId);
        staff.setTenantId(UUID.randomUUID()); // Different tenant

        when(staffAccountRepository.findById(staffId)).thenReturn(Optional.of(staff));

        assertThatThrownBy(() -> staffService.getStaff(tenantId, staffId))
                .isInstanceOf(ResponseStatusException.class)
                .hasMessageContaining("not found");
    }
}
