package org.raddatz.familienarchiv.user;

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.raddatz.familienarchiv.user.AppUser;
import org.raddatz.familienarchiv.user.UserGroup;
import org.raddatz.familienarchiv.user.AppUserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Optional;
import java.util.Set;
import java.util.UUID;

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

@ExtendWith(MockitoExtension.class)
class CustomUserDetailsServiceTest {

    @Mock AppUserRepository userRepository;
    @InjectMocks CustomUserDetailsService service;

    // ─── loadUserByUsername — not found ──────────────────────────────────────

    @Test
    void loadUserByEmail_throwsUsernameNotFoundException_whenUserNotFound() {
        when(userRepository.findByEmail("ghost@example.com")).thenReturn(Optional.empty());

        assertThatThrownBy(() -> service.loadUserByUsername("ghost@example.com"))
                .isInstanceOf(UsernameNotFoundException.class)
                .hasMessageContaining("ghost@example.com");
    }

    // ─── loadUserByUsername — happy path ─────────────────────────────────────

    @Test
    void loadUserByEmail_returnsUserDetails_withEmailAsPrincipal() {
        UserGroup group = UserGroup.builder().id(UUID.randomUUID()).name("Admins")
                .permissions(Set.of("READ_ALL", "WRITE_ALL")).build();
        AppUser user = AppUser.builder().id(UUID.randomUUID())
                .email("admin@example.com").password("hashed").enabled(true)
                .groups(Set.of(group)).build();
        when(userRepository.findByEmail("admin@example.com")).thenReturn(Optional.of(user));

        UserDetails details = service.loadUserByUsername("admin@example.com");

        assertThat(details.getUsername()).isEqualTo("admin@example.com");
        assertThat(details.getAuthorities()).extracting("authority")
                .contains("READ_ALL", "WRITE_ALL");
    }

    @Test
    void loadUserByEmail_returnsEmptyAuthorities_whenUserHasNoGroups() {
        AppUser user = AppUser.builder().id(UUID.randomUUID())
                .email("viewer@example.com").password("hashed").enabled(true)
                .groups(Set.of()).build();
        when(userRepository.findByEmail("viewer@example.com")).thenReturn(Optional.of(user));

        UserDetails details = service.loadUserByUsername("viewer@example.com");

        assertThat(details.getAuthorities()).isEmpty();
    }

    // ─── loadUserByUsername — unknown permission ──────────────────────────────

    @Test
    void loadUserByEmail_grantsUnknownPermission_butLogsWarning() {
        UserGroup group = UserGroup.builder().id(UUID.randomUUID()).name("CustomGroup")
                .permissions(Set.of("UNKNOWN_CUSTOM_PERM")).build();
        AppUser user = AppUser.builder().id(UUID.randomUUID())
                .email("custom@example.com").password("hashed").enabled(true)
                .groups(Set.of(group)).build();
        when(userRepository.findByEmail("custom@example.com")).thenReturn(Optional.of(user));

        UserDetails details = service.loadUserByUsername("custom@example.com");

        assertThat(details.getAuthorities()).extracting("authority")
                .contains("UNKNOWN_CUSTOM_PERM");
    }

    // ─── loadUserByUsername — disabled user ───────────────────────────────────

    @Test
    void loadUserByEmail_returnsDisabledUser_whenUserIsDisabled() {
        AppUser user = AppUser.builder().id(UUID.randomUUID())
                .email("disabled@example.com").password("hashed").enabled(false)
                .groups(Set.of()).build();
        when(userRepository.findByEmail("disabled@example.com")).thenReturn(Optional.of(user));

        UserDetails details = service.loadUserByUsername("disabled@example.com");

        assertThat(details.isEnabled()).isFalse();
    }

    // ─── loadUserByUsername — multi-group permission merge ────────────────────

    @Test
    void loadUserByEmail_mergesPermissionsFromMultipleGroups() {
        UserGroup g1 = UserGroup.builder().id(UUID.randomUUID()).name("Readers")
                .permissions(Set.of("READ_ALL")).build();
        UserGroup g2 = UserGroup.builder().id(UUID.randomUUID()).name("Writers")
                .permissions(Set.of("WRITE_ALL")).build();
        AppUser user = AppUser.builder().id(UUID.randomUUID())
                .email("multi@example.com").password("hashed").enabled(true)
                .groups(Set.of(g1, g2)).build();
        when(userRepository.findByEmail("multi@example.com")).thenReturn(Optional.of(user));

        UserDetails details = service.loadUserByUsername("multi@example.com");

        assertThat(details.getAuthorities()).extracting("authority")
                .containsExactlyInAnyOrder("READ_ALL", "WRITE_ALL");
    }
}
