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.SetPasswordRequest;
import de.cannamanage.api.dto.staff.CreateStaffRequest;
import de.cannamanage.api.dto.staff.StaffResponse;
import de.cannamanage.api.dto.staff.UpdateStaffRequest;
import de.cannamanage.domain.entity.InviteToken;
import de.cannamanage.domain.enums.StaffPermission;
import de.cannamanage.service.repository.InviteTokenRepository;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import java.util.List;
import java.util.Set;
import java.util.UUID;

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

/**
 * Integration test: Token revocation E2E.
 * Verifies that permission changes and deactivation properly revoke tokens.
 */
class TokenRevocationIntegrationTest extends AbstractIntegrationTest {

    @Autowired
    private InviteTokenRepository inviteTokenRepository;

    private UUID tenantId;
    private String adminToken;

    @BeforeEach
    void setUp() {
        tenantId = createTestClub("Token Revocation Club");
        createAdminUser(tenantId, "revoke-admin@test.de", "AdminPass123!");
        adminToken = getAccessToken("revoke-admin@test.de", "AdminPass123!");
    }

    @Test
    @DisplayName("Admin changes staff permissions → old JWT is rejected")
    void adminChangesPermissions_oldJwtRejected() {
        // Create and activate a staff member
        String staffEmail = "revoke-staff1@test.de";
        UUID staffId = createAndActivateStaff(staffEmail,
                Set.of(StaffPermission.RECORD_DISTRIBUTION, StaffPermission.VIEW_STOCK));

        // Staff logs in and gets a valid JWT
        LoginResponse staffLogin = login(staffEmail, "StaffPass123!");
        String oldStaffToken = staffLogin.accessToken();

        // Verify old token works
        ResponseEntity<String> beforeChange = restClient().get()
                .uri("/api/v1/distributions")
                .header("Authorization", "Bearer " + oldStaffToken)
                .retrieve()
                .toEntity(String.class);
        assertThat(beforeChange.getStatusCode().value()).isEqualTo(200);

        // Admin changes staff permissions (triggers revocation)
        UpdateStaffRequest updateRequest = new UpdateStaffRequest(
                "Revoke Staff 1",
                Set.of(StaffPermission.RECORD_DISTRIBUTION),
                null, true);

        restClient().put()
                .uri("/api/v1/staff/" + staffId)
                .header("Authorization", "Bearer " + adminToken)
                .contentType(MediaType.APPLICATION_JSON)
                .body(updateRequest)
                .retrieve()
                .toEntity(StaffResponse.class);

        // Old JWT should now be rejected (revoked)
        ResponseEntity<String> afterChange = restClient().get()
                .uri("/api/v1/distributions")
                .header("Authorization", "Bearer " + oldStaffToken)
                .retrieve()
                .toEntity(String.class);
        assertThat(afterChange.getStatusCode().value()).isIn(401, 403);
    }

    @Test
    @DisplayName("Staff logs in again after permission change → gets new working JWT")
    void staffLoginsAgain_afterPermissionChange_getsWorkingJwt() {
        String staffEmail = "revoke-staff2@test.de";
        UUID staffId = createAndActivateStaff(staffEmail,
                Set.of(StaffPermission.RECORD_DISTRIBUTION, StaffPermission.VIEW_STOCK));

        login(staffEmail, "StaffPass123!");

        // Admin changes permissions
        UpdateStaffRequest updateRequest = new UpdateStaffRequest(
                "Revoke Staff 2",
                Set.of(StaffPermission.RECORD_DISTRIBUTION, StaffPermission.VIEW_MEMBER_LIST),
                null, true);

        restClient().put()
                .uri("/api/v1/staff/" + staffId)
                .header("Authorization", "Bearer " + adminToken)
                .contentType(MediaType.APPLICATION_JSON)
                .body(updateRequest)
                .retrieve()
                .toEntity(StaffResponse.class);

        // Staff logs in again — new JWT should work
        LoginResponse newLogin = login(staffEmail, "StaffPass123!");
        String newToken = newLogin.accessToken();

        ResponseEntity<String> distResponse = restClient().get()
                .uri("/api/v1/distributions")
                .header("Authorization", "Bearer " + newToken)
                .retrieve()
                .toEntity(String.class);
        assertThat(distResponse.getStatusCode().value()).isEqualTo(200);
    }

    @Test
    @DisplayName("Admin deactivates staff → all tokens revoked → 401")
    void adminDeactivatesStaff_allTokensRevoked() {
        String staffEmail = "revoke-staff3@test.de";
        UUID staffId = createAndActivateStaff(staffEmail,
                Set.of(StaffPermission.RECORD_DISTRIBUTION));

        LoginResponse staffLogin = login(staffEmail, "StaffPass123!");
        String staffToken = staffLogin.accessToken();

        // Verify token works before deactivation
        ResponseEntity<String> before = restClient().get()
                .uri("/api/v1/distributions")
                .header("Authorization", "Bearer " + staffToken)
                .retrieve()
                .toEntity(String.class);
        assertThat(before.getStatusCode().value()).isEqualTo(200);

        // Admin deactivates staff
        restClient().delete()
                .uri("/api/v1/staff/" + staffId)
                .header("Authorization", "Bearer " + adminToken)
                .retrieve()
                .toEntity(Void.class);

        // Old token should now be rejected
        ResponseEntity<String> after = restClient().get()
                .uri("/api/v1/distributions")
                .header("Authorization", "Bearer " + staffToken)
                .retrieve()
                .toEntity(String.class);
        assertThat(after.getStatusCode().value()).isIn(401, 403);
    }

    @Test
    @DisplayName("Deactivated staff cannot login")
    void deactivatedStaff_cannotLogin() {
        String staffEmail = "revoke-staff4@test.de";
        UUID staffId = createAndActivateStaff(staffEmail,
                Set.of(StaffPermission.RECORD_DISTRIBUTION));

        // Deactivate
        restClient().delete()
                .uri("/api/v1/staff/" + staffId)
                .header("Authorization", "Bearer " + adminToken)
                .retrieve()
                .toEntity(Void.class);

        // Try to login — should fail
        ResponseEntity<String> response = restClient().post()
                .uri("/api/v1/auth/login")
                .contentType(MediaType.APPLICATION_JSON)
                .body(new LoginRequest(staffEmail, "StaffPass123!"))
                .retrieve()
                .toEntity(String.class);

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

    // --- Helper ---

    private UUID createAndActivateStaff(String email, Set<StaffPermission> permissions) {
        CreateStaffRequest createRequest = new CreateStaffRequest(
                email, "Staff " + email.split("@")[0], permissions, null);

        ResponseEntity<StaffResponse> createResp = restClient().post()
                .uri("/api/v1/staff")
                .header("Authorization", "Bearer " + adminToken)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createRequest)
                .retrieve()
                .toEntity(StaffResponse.class);

        UUID staffId = createResp.getBody().id();

        // Find the invite token
        List<InviteToken> tokens = inviteTokenRepository.findAll();
        InviteToken inviteToken = tokens.stream()
                .filter(t -> t.getUsedAt() == null)
                .reduce((first, second) -> second)
                .orElseThrow(() -> new AssertionError("No invite token found"));

        // Set password to activate
        restClient().post()
                .uri("/api/v1/auth/set-password")
                .contentType(MediaType.APPLICATION_JSON)
                .body(new SetPasswordRequest(inviteToken.getToken(), "StaffPass123!"))
                .retrieve()
                .toEntity(String.class);

        return staffId;
    }
}
