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

/**
 * Unit tests for the role context (roleContext.svelte.ts).
 *
 * This module uses Svelte 5 $state runes for reactive state management.
 * We test it through its public exported API and mock the underlying
 * roles API module to isolate the context logic.
 */

// Mock the roles API module
const mockGetMyRoles = vi.fn();
const mockSwitchRole = vi.fn();
vi.mock('$lib/features/roles/api/roles', () => ({
	getMyRoles: (...args: unknown[]) => mockGetMyRoles(...args),
	switchRole: (...args: unknown[]) => mockSwitchRole(...args)
}));

describe('roleContext', () => {
	let roleContext: typeof import('$lib/features/roles/roleContext.svelte');

	beforeEach(async () => {
		vi.clearAllMocks();

		// Fresh import to reset $state between tests
		vi.resetModules();
		roleContext = await import('$lib/features/roles/roleContext.svelte');
	});

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

	// ==========================================================================
	// Initial state
	// ==========================================================================
	describe('initial state', () => {
		it('should have no roles initially', () => {
			expect(roleContext.getRoles()).toEqual([]);
		});

		it('should have null activeRole initially', () => {
			expect(roleContext.getActiveRole()).toBeNull();
		});

		it('should have null activeRoleLabel initially', () => {
			expect(roleContext.getActiveRoleLabel()).toBeNull();
		});

		it('should not be loading initially', () => {
			expect(roleContext.getIsLoading()).toBe(false);
		});

		it('should not be switching initially', () => {
			expect(roleContext.getIsSwitching()).toBe(false);
		});

		it('should not have multiple roles initially', () => {
			expect(roleContext.hasMultipleRoles()).toBe(false);
		});
	});

	// ==========================================================================
	// fetchRoles
	// ==========================================================================
	describe('fetchRoles', () => {
		it('should load roles from API and set state', async () => {
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [
					{ value: 'ROLE_ADMIN', label: 'Administrateur' },
					{ value: 'ROLE_TEACHER', label: 'Enseignant' }
				],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Administrateur'
			});

			await roleContext.fetchRoles();

			expect(mockGetMyRoles).toHaveBeenCalledOnce();
			expect(roleContext.getRoles()).toHaveLength(2);
			expect(roleContext.getActiveRole()).toBe('ROLE_ADMIN');
			expect(roleContext.getActiveRoleLabel()).toBe('Administrateur');
			expect(roleContext.getIsLoading()).toBe(false);
		});

		it('should guard against double loading (isFetched)', async () => {
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [{ value: 'ROLE_ADMIN', label: 'Admin' }],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Admin'
			});

			// First call loads data
			await roleContext.fetchRoles();
			expect(mockGetMyRoles).toHaveBeenCalledOnce();

			// Second call should be a no-op due to isFetched guard
			await roleContext.fetchRoles();
			expect(mockGetMyRoles).toHaveBeenCalledOnce();
		});

		it('should handle API errors gracefully without throwing', async () => {
			const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
			mockGetMyRoles.mockRejectedValueOnce(new Error('Network error'));

			// Should not throw
			await roleContext.fetchRoles();

			expect(consoleSpy).toHaveBeenCalledWith(
				'[roleContext] Failed to fetch roles:',
				expect.any(Error)
			);
			// State should remain empty
			expect(roleContext.getRoles()).toEqual([]);
			expect(roleContext.getActiveRole()).toBeNull();
			expect(roleContext.getIsLoading()).toBe(false);

			consoleSpy.mockRestore();
		});
	});

	// ==========================================================================
	// switchTo
	// ==========================================================================
	describe('switchTo', () => {
		it('should return true immediately when switching to same role', async () => {
			// First, load roles so activeRole is set
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [{ value: 'ROLE_ADMIN', label: 'Admin' }],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Admin'
			});
			await roleContext.fetchRoles();

			// Switch to the same role
			const result = await roleContext.switchTo('ROLE_ADMIN');

			expect(result).toBe(true);
			// API should NOT be called for same-role switch
			expect(mockSwitchRole).not.toHaveBeenCalled();
		});

		it('should call API and update state when switching to different role', async () => {
			// Load initial roles
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [
					{ value: 'ROLE_ADMIN', label: 'Admin' },
					{ value: 'ROLE_TEACHER', label: 'Enseignant' }
				],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Admin'
			});
			await roleContext.fetchRoles();

			// Switch to a different role
			mockSwitchRole.mockResolvedValueOnce({
				activeRole: 'ROLE_TEACHER',
				activeRoleLabel: 'Enseignant'
			});

			const result = await roleContext.switchTo('ROLE_TEACHER');

			expect(result).toBe(true);
			expect(mockSwitchRole).toHaveBeenCalledWith('ROLE_TEACHER');
			expect(roleContext.getActiveRole()).toBe('ROLE_TEACHER');
			expect(roleContext.getActiveRoleLabel()).toBe('Enseignant');
			expect(roleContext.getIsSwitching()).toBe(false);
		});

		it('should return false when the API call fails', async () => {
			const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

			// Load initial roles
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [
					{ value: 'ROLE_ADMIN', label: 'Admin' },
					{ value: 'ROLE_TEACHER', label: 'Enseignant' }
				],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Admin'
			});
			await roleContext.fetchRoles();

			// Switch fails
			mockSwitchRole.mockRejectedValueOnce(new Error('Server error'));

			const result = await roleContext.switchTo('ROLE_TEACHER');

			expect(result).toBe(false);
			expect(roleContext.getActiveRole()).toBe('ROLE_ADMIN'); // unchanged
			expect(roleContext.getIsSwitching()).toBe(false);

			consoleSpy.mockRestore();
		});
	});

	// ==========================================================================
	// hasMultipleRoles
	// ==========================================================================
	describe('hasMultipleRoles', () => {
		it('should return true when user has more than one role', async () => {
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [
					{ value: 'ROLE_ADMIN', label: 'Admin' },
					{ value: 'ROLE_TEACHER', label: 'Enseignant' }
				],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Admin'
			});
			await roleContext.fetchRoles();

			expect(roleContext.hasMultipleRoles()).toBe(true);
		});

		it('should return false when user has zero roles', () => {
			// No fetch, so roles is empty
			expect(roleContext.hasMultipleRoles()).toBe(false);
		});

		it('should return false when user has exactly one role', async () => {
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [{ value: 'ROLE_ADMIN', label: 'Admin' }],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Admin'
			});
			await roleContext.fetchRoles();

			expect(roleContext.hasMultipleRoles()).toBe(false);
		});
	});

	// ==========================================================================
	// resetRoleContext
	// ==========================================================================
	describe('resetRoleContext', () => {
		it('should clear all state back to initial values', async () => {
			// Load some data first
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [
					{ value: 'ROLE_ADMIN', label: 'Admin' },
					{ value: 'ROLE_TEACHER', label: 'Enseignant' }
				],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Admin'
			});
			await roleContext.fetchRoles();

			// Verify state is set
			expect(roleContext.getRoles()).toHaveLength(2);
			expect(roleContext.getActiveRole()).toBe('ROLE_ADMIN');

			// Reset
			roleContext.resetRoleContext();

			expect(roleContext.getRoles()).toEqual([]);
			expect(roleContext.getActiveRole()).toBeNull();
			expect(roleContext.getActiveRoleLabel()).toBeNull();
			expect(roleContext.hasMultipleRoles()).toBe(false);
		});

		it('should allow fetchRoles to be called again after reset', async () => {
			// Load initial data
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [{ value: 'ROLE_ADMIN', label: 'Admin' }],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Admin'
			});
			await roleContext.fetchRoles();
			expect(mockGetMyRoles).toHaveBeenCalledOnce();

			// Reset clears isFetched
			roleContext.resetRoleContext();

			// Now fetchRoles should call the API again
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [{ value: 'ROLE_TEACHER', label: 'Enseignant' }],
				activeRole: 'ROLE_TEACHER',
				activeRoleLabel: 'Enseignant'
			});
			await roleContext.fetchRoles();

			expect(mockGetMyRoles).toHaveBeenCalledTimes(2);
			expect(roleContext.getActiveRole()).toBe('ROLE_TEACHER');
		});
	});

	// ==========================================================================
	// getIsLoading / getIsSwitching
	// ==========================================================================
	describe('loading and switching state', () => {
		it('should set isLoading to true during fetchRoles and false after', async () => {
			// Use a deferred promise to control resolution timing
			let resolvePromise: (value: unknown) => void;
			const pendingPromise = new Promise((resolve) => {
				resolvePromise = resolve;
			});
			mockGetMyRoles.mockReturnValueOnce(pendingPromise);

			const fetchPromise = roleContext.fetchRoles();

			// While the API call is pending, isLoading should be true
			expect(roleContext.getIsLoading()).toBe(true);

			// Resolve the API call
			resolvePromise!({
				roles: [{ value: 'ROLE_ADMIN', label: 'Admin' }],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Admin'
			});

			await fetchPromise;

			expect(roleContext.getIsLoading()).toBe(false);
		});

		it('should set isSwitching to true during switchTo and false after', async () => {
			// Load roles first so activeRole is set
			mockGetMyRoles.mockResolvedValueOnce({
				roles: [
					{ value: 'ROLE_ADMIN', label: 'Admin' },
					{ value: 'ROLE_TEACHER', label: 'Enseignant' }
				],
				activeRole: 'ROLE_ADMIN',
				activeRoleLabel: 'Admin'
			});
			await roleContext.fetchRoles();

			// Use a deferred promise for switch
			let resolvePromise: (value: unknown) => void;
			const pendingPromise = new Promise((resolve) => {
				resolvePromise = resolve;
			});
			mockSwitchRole.mockReturnValueOnce(pendingPromise);

			const switchPromise = roleContext.switchTo('ROLE_TEACHER');

			// While switching, isSwitching should be true
			expect(roleContext.getIsSwitching()).toBe(true);

			// Resolve
			resolvePromise!({
				activeRole: 'ROLE_TEACHER',
				activeRoleLabel: 'Enseignant'
			});

			await switchPromise;

			expect(roleContext.getIsSwitching()).toBe(false);
		});
	});
});
