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

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

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

import {
	fetchStudents,
	fetchClasses,
	createStudent,
	changeStudentClass
} from '$lib/features/students/api/students';

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

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

	// ==========================================================================
	// fetchStudents
	// ==========================================================================
	describe('fetchStudents', () => {
		it('should return members and totalItems on success', async () => {
			const mockStudents = [
				{
					id: 'student-1',
					firstName: 'Marie',
					lastName: 'Dupont',
					email: null,
					classId: 'class-1',
					className: '6ème A',
					classLevel: 'sixieme',
					statut: 'inscrit',
					studentNumber: null,
					dateNaissance: null
				}
			];

			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: true,
				json: () =>
					Promise.resolve({
						'hydra:member': mockStudents,
						'hydra:totalItems': 1
					})
			});

			const result = await fetchStudents({ page: 1, itemsPerPage: 30 });

			expect(mockAuthenticatedFetch).toHaveBeenCalledWith(
				'http://test.classeo.local:18000/api/students?page=1&itemsPerPage=30',
				{}
			);
			expect(result.members).toHaveLength(1);
			expect(result.members[0]!.firstName).toBe('Marie');
			expect(result.totalItems).toBe(1);
		});

		it('should pass search and classId params when provided', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: true,
				json: () =>
					Promise.resolve({
						'hydra:member': [],
						'hydra:totalItems': 0
					})
			});

			await fetchStudents({
				page: 2,
				itemsPerPage: 30,
				search: 'Dupont',
				classId: 'class-1'
			});

			expect(mockAuthenticatedFetch).toHaveBeenCalledWith(
				'http://test.classeo.local:18000/api/students?page=2&itemsPerPage=30&search=Dupont&classId=class-1',
				{}
			);
		});

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

			await expect(fetchStudents({ page: 1, itemsPerPage: 30 })).rejects.toThrow(
				'Erreur lors du chargement des élèves'
			);
		});
	});

	// ==========================================================================
	// fetchClasses
	// ==========================================================================
	describe('fetchClasses', () => {
		it('should return classes array on success', async () => {
			const mockClasses = [
				{ id: 'class-1', name: '6ème A', level: 'sixieme' },
				{ id: 'class-2', name: '5ème B', level: 'cinquieme' }
			];

			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: true,
				json: () => Promise.resolve({ 'hydra:member': mockClasses })
			});

			const result = await fetchClasses();

			expect(mockAuthenticatedFetch).toHaveBeenCalledWith(
				'http://test.classeo.local:18000/api/classes?itemsPerPage=200'
			);
			expect(result).toHaveLength(2);
			expect(result[0]!.name).toBe('6ème A');
		});

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

			await expect(fetchClasses()).rejects.toThrow('Erreur lors du chargement des classes');
		});
	});

	// ==========================================================================
	// createStudent
	// ==========================================================================
	describe('createStudent', () => {
		it('should return created student on success', async () => {
			const created = {
				id: 'new-student-id',
				firstName: 'Marie',
				lastName: 'Dupont',
				email: null,
				classId: 'class-1',
				className: '6ème A',
				classLevel: 'sixieme',
				statut: 'inscrit',
				studentNumber: null,
				dateNaissance: null
			};

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

			const result = await createStudent({
				firstName: 'Marie',
				lastName: 'Dupont',
				classId: 'class-1'
			});

			expect(mockAuthenticatedFetch).toHaveBeenCalledWith(
				'http://test.classeo.local:18000/api/students',
				expect.objectContaining({
					method: 'POST',
					headers: { 'Content-Type': 'application/json' },
					body: JSON.stringify({
						firstName: 'Marie',
						lastName: 'Dupont',
						classId: 'class-1'
					})
				})
			);
			expect(result.id).toBe('new-student-id');
			expect(result.firstName).toBe('Marie');
		});

		it('should include optional fields when provided', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: true,
				json: () =>
					Promise.resolve({
						id: 'new-id',
						firstName: 'Marie',
						lastName: 'Dupont',
						email: 'marie@example.com',
						classId: 'class-1',
						className: '6ème A',
						classLevel: 'sixieme',
						statut: 'pending',
						studentNumber: '12345',
						dateNaissance: '2015-06-15'
					})
			});

			await createStudent({
				firstName: 'Marie',
				lastName: 'Dupont',
				classId: 'class-1',
				email: 'marie@example.com',
				dateNaissance: '2015-06-15',
				studentNumber: '12345'
			});

			expect(mockAuthenticatedFetch).toHaveBeenCalledWith(
				'http://test.classeo.local:18000/api/students',
				expect.objectContaining({
					body: JSON.stringify({
						firstName: 'Marie',
						lastName: 'Dupont',
						classId: 'class-1',
						email: 'marie@example.com',
						dateNaissance: '2015-06-15',
						studentNumber: '12345'
					})
				})
			);
		});

		it('should throw with hydra:description on error', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: false,
				status: 422,
				json: () =>
					Promise.resolve({
						'hydra:description': 'Cet email est déjà utilisé.'
					})
			});

			await expect(
				createStudent({ firstName: 'Marie', lastName: 'Dupont', classId: 'class-1' })
			).rejects.toThrow('Cet email est déjà utilisé.');
		});

		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(
				createStudent({ firstName: 'Marie', lastName: 'Dupont', classId: 'class-1' })
			).rejects.toThrow('Erreur lors de la création (500)');
		});
	});

	// ==========================================================================
	// changeStudentClass
	// ==========================================================================
	describe('changeStudentClass', () => {
		it('should call PATCH endpoint with correct body', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: true
			});

			await changeStudentClass('student-1', 'class-2');

			expect(mockAuthenticatedFetch).toHaveBeenCalledWith(
				'http://test.classeo.local:18000/api/students/student-1/class',
				expect.objectContaining({
					method: 'PATCH',
					headers: { 'Content-Type': 'application/merge-patch+json' },
					body: JSON.stringify({ classId: 'class-2' })
				})
			);
		});

		it('should throw with hydra:description on error', async () => {
			mockAuthenticatedFetch.mockResolvedValueOnce({
				ok: false,
				status: 404,
				json: () =>
					Promise.resolve({
						'hydra:description': 'Élève non trouvé.'
					})
			});

			await expect(changeStudentClass('student-1', 'class-2')).rejects.toThrow(
				'Élève non trouvé.'
			);
		});

		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(changeStudentClass('student-1', 'class-2')).rejects.toThrow(
				'Erreur lors du changement de classe (500)'
			);
		});
	});
});
