package com.magistr.app.config.auth;

import com.magistr.app.model.AuthRefreshToken;
import com.magistr.app.model.Role;
import com.magistr.app.model.User;
import com.magistr.app.repository.AuthRefreshTokenRepository;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class RefreshTokenServiceTest {

    @Test
    void storesOnlyTokenHash() {
        AuthRefreshTokenRepository repository = mock(AuthRefreshTokenRepository.class);
        when(repository.save(any(AuthRefreshToken.class))).thenAnswer(invocation -> invocation.getArgument(0));
        RefreshTokenService service = new RefreshTokenService(repository, properties());

        String rawToken = service.createSession(user(), "magistr", "Browser", "127.0.0.1");

        ArgumentCaptor<AuthRefreshToken> captor = ArgumentCaptor.forClass(AuthRefreshToken.class);
        verify(repository).save(captor.capture());
        AuthRefreshToken stored = captor.getValue();

        assertThat(stored.getTokenHash()).isNotEqualTo(rawToken);
        assertThat(stored.getTokenHash()).hasSize(64);
        assertThat(stored.getTenant()).isEqualTo("magistr");
        assertThat(stored.getExpiresAt()).isAfter(Instant.now());
    }

    @Test
    void rotatesRefreshTokenAndRejectsReplay() {
        AuthRefreshTokenRepository repository = mock(AuthRefreshTokenRepository.class);
        when(repository.save(any(AuthRefreshToken.class))).thenAnswer(invocation -> invocation.getArgument(0));
        RefreshTokenService service = new RefreshTokenService(repository, properties());
        String rawToken = "raw-refresh-token";
        AuthRefreshToken stored = activeToken(service.hashToken(rawToken));
        when(repository.findByTokenHashForUpdate(service.hashToken(rawToken))).thenReturn(Optional.of(stored));

        Optional<RefreshTokenRotation> rotation = service.rotate(rawToken, "magistr", "Browser", "127.0.0.1");

        assertThat(rotation).isPresent();
        assertThat(rotation.get().refreshToken()).isNotEqualTo(rawToken);
        assertThat(stored.getRevokedAt()).isNotNull();
        assertThat(stored.getRotatedToTokenHash()).hasSize(64);

        Optional<RefreshTokenRotation> replay = service.rotate(rawToken, "magistr", "Browser", "127.0.0.1");
        assertThat(replay).isEmpty();
        verify(repository, times(2)).save(any(AuthRefreshToken.class));
    }

    @Test
    void rejectsTokenFromDifferentTenant() {
        AuthRefreshTokenRepository repository = mock(AuthRefreshTokenRepository.class);
        RefreshTokenService service = new RefreshTokenService(repository, properties());
        String rawToken = "raw-refresh-token";
        when(repository.findByTokenHashForUpdate(service.hashToken(rawToken)))
                .thenReturn(Optional.of(activeToken(service.hashToken(rawToken))));

        assertThat(service.rotate(rawToken, "n8n", "Browser", "127.0.0.1")).isEmpty();
    }

    @Test
    void logoutRevokesActiveToken() {
        AuthRefreshTokenRepository repository = mock(AuthRefreshTokenRepository.class);
        when(repository.save(any(AuthRefreshToken.class))).thenAnswer(invocation -> invocation.getArgument(0));
        RefreshTokenService service = new RefreshTokenService(repository, properties());
        String rawToken = "raw-refresh-token";
        AuthRefreshToken stored = activeToken(service.hashToken(rawToken));
        when(repository.findByTokenHash(service.hashToken(rawToken))).thenReturn(Optional.of(stored));

        service.revoke(rawToken, "magistr");

        assertThat(stored.getRevokedAt()).isNotNull();
        verify(repository).save(stored);
    }

    private JwtProperties properties() {
        JwtProperties properties = new JwtProperties();
        properties.setRefreshTtl(Duration.ofDays(7));
        return properties;
    }

    private AuthRefreshToken activeToken(String hash) {
        AuthRefreshToken token = new AuthRefreshToken();
        token.setUser(user());
        token.setTenant("magistr");
        token.setTokenHash(hash);
        token.setIssuedAt(Instant.now().minus(1, ChronoUnit.MINUTES));
        token.setExpiresAt(Instant.now().plus(1, ChronoUnit.DAYS));
        return token;
    }

    private User user() {
        User user = new User();
        user.setId(10L);
        user.setUsername("admin");
        user.setRole(Role.ADMIN);
        user.setDepartmentId(2L);
        user.setFullName("Администратор");
        user.setJobTitle("Администратор");
        user.setPassword("hash");
        return user;
    }
}
