import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

/**
 * Unit tests for the roles API module.
 *
 * Tests getMyRoles(), switchRole(), and updateUserRoles() which all rely on
 * authenticatedFetch from $lib/auth and getApiBaseUrl from $lib/api.
 */

// Mock $lib/api
vi.mock('$lib/api', () => ({
	getApiBaseUrl: () => 'http://test.classeo.local:18000/api'
}));

// Mock $lib/auth - authenticatedFetch is the primary dependency
const mockAuthenticatedFetch = vi.fn();
vi.mock('$lib/auth', () => ({
	authenticatedFetch: (...args: unknown[]) => mockAuthenticatedFetch(...args)
}));

import { getMyRoles, switchRole, updateUserRoles } from '$lib/features/roles/api/roles';

describe('roles API', () => {
	beforeEach(() => {
		vi.clearAllMocks();
	});

	afterEach(() => {
		vi.restoreAllMocks();
	});

	// ==========================================================================
	// getMyRoles
	// ==========================================================================
	describe('getMyRoles', () => {
		it('should return roles array and activeRole on success', async () => {
			const mockResponse = {
				roles: [
					{ value: 'ROLE_ADMIN', label: 'Administrateur' },
					{ value: 'ROLE_TEACHER', label: 'Enseignant' }
				],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Administrateur'
			};

			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: true,
				json: () => Promise.resolve(mockResponse)
			});

			const result = await getMyRoles();

			expect(mockAuthenticatedFetch).toHaveBeenCalledWith(
				'http://test.classeo.local:18000/api/me/roles'
			);
			expect(result.roles).toHaveLength(2);
			expect(result.roles[0]).toEqual({ value: 'ROLE_ADMIN', label: 'Administrateur' });
			expect(result.activeRole).toBe('ROLE_ADMIN');
			expect(result.activeRoleLabel).toBe('Administrateur');
		});

		it('should throw Error when the API response is not ok', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: false,
				status: 500
			});

			await expect(getMyRoles()).rejects.toThrow('Failed to fetch roles');
		});
	});

	// ==========================================================================
	// switchRole
	// ==========================================================================
	describe('switchRole', () => {
		it('should return new activeRole on success', async () => {
			const mockResponse = {
				activeRole: 'ROLE_TEACHER',
				activeRoleLabel: 'Enseignant'
			};

			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: true,
				json: () => Promise.resolve(mockResponse)
			});

			const result = await switchRole('ROLE_TEACHER');

			expect(mockAuthenticatedFetch).toHaveBeenCalledWith(
				'http://test.classeo.local:18000/api/me/switch-role',
				expect.objectContaining({
					method: 'POST',
					headers: { 'Content-Type': 'application/json' },
					body: JSON.stringify({ role: 'ROLE_TEACHER' })
				})
			);
			expect(result.activeRole).toBe('ROLE_TEACHER');
			expect(result.activeRoleLabel).toBe('Enseignant');
		});

		it('should throw Error when the API response is not ok', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: false,
				status: 400
			});

			await expect(switchRole('ROLE_INVALID')).rejects.toThrow('Failed to switch role');
		});
	});

	// ==========================================================================
	// updateUserRoles
	// ==========================================================================
	describe('updateUserRoles', () => {
		it('should complete without error on 2xx success', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: true,
				status: 204
			});

			// Should resolve without throwing
			await expect(
				updateUserRoles('user-uuid-123', ['ROLE_ADMIN', 'ROLE_TEACHER'])
			).resolves.toBeUndefined();

			expect(mockAuthenticatedFetch).toHaveBeenCalledWith(
				'http://test.classeo.local:18000/api/users/user-uuid-123/roles',
				expect.objectContaining({
					method: 'PUT',
					headers: { 'Content-Type': 'application/json' },
					body: JSON.stringify({ roles: ['ROLE_ADMIN', 'ROLE_TEACHER'] })
				})
			);
		});

		it('should throw with hydra:description when present in error response', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: false,
				status: 422,
				json: () =>
					Promise.resolve({
						'hydra:description': 'Le rôle ROLE_INVALID est inconnu.'
					})
			});

			await expect(
				updateUserRoles('user-uuid-123', ['ROLE_INVALID'])
			).rejects.toThrow('Le rôle ROLE_INVALID est inconnu.');
		});

		it('should throw with detail when present in error response', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: false,
				status: 403,
				json: () =>
					Promise.resolve({
						detail: 'Accès refusé.'
					})
			});

			await expect(
				updateUserRoles('user-uuid-123', ['ROLE_ADMIN'])
			).rejects.toThrow('Accès refusé.');
		});

		it('should throw generic message when error body is not valid JSON', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: false,
				status: 500,
				json: () => Promise.reject(new Error('Unexpected token'))
			});

			await expect(
				updateUserRoles('user-uuid-123', ['ROLE_ADMIN'])
			).rejects.toThrow('Erreur lors de la mise à jour des rôles (500)');
		});
	});
});
