package de.cannamanage.api.integration;

import de.cannamanage.api.dto.auth.LoginRequest;
import de.cannamanage.api.dto.auth.LoginResponse;
import de.cannamanage.api.dto.auth.RefreshRequest;
import de.cannamanage.domain.entity.User;
import de.cannamanage.domain.enums.UserRole;
import org.junit.jupiter.api.*;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Integration test: Full authentication flow.
 * Tests login, token refresh, revocation, and error cases.
 */
class AuthIntegrationTest extends AbstractIntegrationTest {

    private UUID tenantId;
    private static final String ADMIN_EMAIL = "auth-admin@test.de";
    private static final String ADMIN_PASSWORD = "AdminPass123!";

    @BeforeEach
    void setUp() {
        tenantId = createTestClub("Auth Test Club");
        createAdminUser(tenantId, ADMIN_EMAIL, ADMIN_PASSWORD);
    }

    @Test
    @DisplayName("Login with valid credentials returns JWT + refresh token")
    void loginWithValidCredentials_returnsTokens() {
        LoginResponse response = login(ADMIN_EMAIL, ADMIN_PASSWORD);

        assertThat(response).isNotNull();
        assertThat(response.accessToken()).isNotBlank();
        assertThat(response.refreshToken()).isNotBlank();
        assertThat(response.expiresIn()).isEqualTo(3600L);
        assertThat(response.role()).isEqualTo("ADMIN");
    }

    @Test
    @DisplayName("Access protected endpoint with JWT returns 200")
    void accessProtectedEndpoint_withValidJwt_returns200() {
        String token = getAccessToken(ADMIN_EMAIL, ADMIN_PASSWORD);

        ResponseEntity<String> response = restClient().get()
                .uri("/api/v1/members")
                .header("Authorization", "Bearer " + token)
                .retrieve()
                .toEntity(String.class);

        assertThat(response.getStatusCode().value()).isEqualTo(200);
    }

    @Test
    @DisplayName("Refresh token returns new JWT pair")
    void refreshToken_returnsNewTokenPair() {
        LoginResponse loginResponse = login(ADMIN_EMAIL, ADMIN_PASSWORD);

        ResponseEntity<LoginResponse> response = restClient().post()
                .uri("/api/v1/auth/refresh")
                .contentType(MediaType.APPLICATION_JSON)
                .body(new RefreshRequest(loginResponse.refreshToken()))
                .retrieve()
                .toEntity(LoginResponse.class);

        assertThat(response.getStatusCode().value()).isEqualTo(200);
        LoginResponse refreshed = response.getBody();
        assertThat(refreshed).isNotNull();
        assertThat(refreshed.accessToken()).isNotBlank();
        assertThat(refreshed.refreshToken()).isNotBlank();
        assertThat(refreshed.accessToken()).isNotEqualTo(loginResponse.accessToken());
        assertThat(refreshed.refreshToken()).isNotEqualTo(loginResponse.refreshToken());
    }

    @Test
    @DisplayName("Old refresh token is invalidated after rotation")
    void oldRefreshToken_afterRotation_isInvalid() {
        LoginResponse loginResponse = login(ADMIN_EMAIL, ADMIN_PASSWORD);
        String oldRefreshToken = loginResponse.refreshToken();

        // Use refresh token once (rotation)
        restClient().post()
                .uri("/api/v1/auth/refresh")
                .contentType(MediaType.APPLICATION_JSON)
                .body(new RefreshRequest(oldRefreshToken))
                .retrieve()
                .toEntity(LoginResponse.class);

        // Try to use the old refresh token again — should fail
        ResponseEntity<String> response = restClient().post()
                .uri("/api/v1/auth/refresh")
                .contentType(MediaType.APPLICATION_JSON)
                .body(new RefreshRequest(oldRefreshToken))
                .retrieve()
                .toEntity(String.class);

        assertThat(response.getStatusCode().value()).isEqualTo(401);
    }

    @Test
    @DisplayName("Login with wrong password returns 401")
    void loginWithWrongPassword_returns401() {
        ResponseEntity<String> response = restClient().post()
                .uri("/api/v1/auth/login")
                .contentType(MediaType.APPLICATION_JSON)
                .body(new LoginRequest(ADMIN_EMAIL, "WrongPassword!"))
                .retrieve()
                .toEntity(String.class);

        assertThat(response.getStatusCode().value()).isEqualTo(401);
    }

    @Test
    @DisplayName("Login with non-existent email returns 401")
    void loginWithNonExistentEmail_returns401() {
        ResponseEntity<String> response = restClient().post()
                .uri("/api/v1/auth/login")
                .contentType(MediaType.APPLICATION_JSON)
                .body(new LoginRequest("nobody@test.de", "whatever"))
                .retrieve()
                .toEntity(String.class);

        assertThat(response.getStatusCode().value()).isEqualTo(401);
    }

    @Test
    @DisplayName("Access protected endpoint without token returns 401/403")
    void accessProtectedEndpoint_withoutToken_returnsUnauthorized() {
        ResponseEntity<String> response = restClient().get()
                .uri("/api/v1/members")
                .retrieve()
                .toEntity(String.class);

        assertThat(response.getStatusCode().value()).isIn(401, 403);
    }

    @Test
    @DisplayName("Inactive user cannot login")
    void inactiveUser_cannotLogin() {
        User inactiveUser = new User();
        inactiveUser.setTenantId(tenantId);
        inactiveUser.setEmail("inactive@test.de");
        inactiveUser.setPasswordHash(passwordEncoder.encode("Test123!"));
        inactiveUser.setRole(UserRole.ROLE_ADMIN);
        inactiveUser.setActive(false);
        userRepository.save(inactiveUser);

        ResponseEntity<String> response = restClient().post()
                .uri("/api/v1/auth/login")
                .contentType(MediaType.APPLICATION_JSON)
                .body(new LoginRequest("inactive@test.de", "Test123!"))
                .retrieve()
                .toEntity(String.class);

        assertThat(response.getStatusCode().value()).isEqualTo(401);
    }
}
