package com.codebyte.api.auth.service;

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.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

import com.codebyte.api.auth.api.dto.ForgotPasswordRequest;
import com.codebyte.api.auth.api.dto.ResetPasswordRequest;
import com.codebyte.api.auth.domain.TokenType;
import com.codebyte.api.auth.domain.User;
import com.codebyte.api.auth.repository.UserRepository;
import com.codebyte.api.config.AppProperties;
import com.codebyte.api.support.TestAppProperties;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.password.PasswordEncoder;

@ExtendWith(MockitoExtension.class)
class PasswordResetServiceTest {

    @Mock private UserRepository userRepository;
    @Mock private PasswordEncoder passwordEncoder;
    @Mock private TokenService tokenService;
    @Mock private RefreshTokenService refreshTokenService;
    @Mock private MailService mailService;

    private final AppProperties properties = TestAppProperties.defaults();

    private PasswordResetService service() {
        return new PasswordResetService(
                userRepository,
                passwordEncoder,
                tokenService,
                refreshTokenService,
                mailService,
                properties);
    }

    @Test
    void should_issue_reset_token_and_email_when_user_exists() {
        User user = new User("user@example.com", "hash");
        when(userRepository.findByEmailIgnoreCase("user@example.com"))
                .thenReturn(Optional.of(user));
        when(tokenService.issue(eq(user), eq(TokenType.PASSWORD_RESET), any()))
                .thenReturn("reset-token");

        service().forgot(new ForgotPasswordRequest("User@Example.com"));

        verify(tokenService).invalidateActive(user, TokenType.PASSWORD_RESET);
        verify(mailService).sendPasswordResetEmail("user@example.com", "reset-token");
    }

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

        service().forgot(new ForgotPasswordRequest("ghost@example.com"));

        verifyNoInteractions(tokenService, mailService);
    }

    @Test
    void should_reset_password_and_revoke_all_refresh_tokens() {
        User user = new User("user@example.com", "oldHash");
        when(tokenService.consume("tok", TokenType.PASSWORD_RESET)).thenReturn(user);
        when(passwordEncoder.encode("new valid password")).thenReturn("newHash");

        service().reset(new ResetPasswordRequest("tok", "new valid password"));

        assertThat(user.getPassword()).isEqualTo("newHash");
        verify(refreshTokenService).revokeAll(user);
        verify(tokenService).invalidateActive(user, TokenType.PASSWORD_RESET);
    }

    @Test
    void should_propagate_invalid_reset_token() {
        when(tokenService.consume(eq("bad"), eq(TokenType.PASSWORD_RESET)))
                .thenThrow(new InvalidTokenException(TokenType.PASSWORD_RESET));

        assertThatThrownBy(
                        () ->
                                service()
                                        .reset(
                                                new ResetPasswordRequest(
                                                        "bad", "new valid password")))
                .isInstanceOf(InvalidTokenException.class);
    }
}
