/**
 * Example React Native Service Test
 *
 * This demonstrates best practices for testing services:
 * - Mock fetch/API calls
 * - Test async operations
 * - Test error handling
 * - Test storage operations
 * - Use MSW for API mocking (optional)
 */

import { authService } from '../authService';
import { tokenManager } from '../tokenManager';
import * as SecureStore from 'expo-secure-store';

// Mock dependencies
jest.mock('expo-secure-store');
jest.mock('../tokenManager');

// Mock data
const mockTokens = {
	appToken: 'mock-app-token-12345',
	refreshToken: 'mock-refresh-token-12345',
	manaToken: 'mock-mana-token-12345',
};

const mockUser = {
	id: 'user-123',
	email: 'test@example.com',
};

describe('authService', () => {
	beforeEach(() => {
		jest.clearAllMocks();
		global.fetch = jest.fn();
	});

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

	describe('signIn', () => {
		it('should sign in successfully with valid credentials', async () => {
			// Arrange
			const mockResponse = {
				ok: true,
				status: 200,
				json: async () => ({
					success: true,
					...mockTokens,
					user: mockUser,
				}),
			};

			(global.fetch as jest.Mock).mockResolvedValue(mockResponse);
			(SecureStore.setItemAsync as jest.Mock).mockResolvedValue(undefined);

			// Act
			const result = await authService.signIn('test@example.com', 'password123');

			// Assert
			expect(result.success).toBe(true);
			expect(result.user).toEqual(mockUser);
			expect(global.fetch).toHaveBeenCalledWith(
				expect.stringContaining('/auth/signin'),
				expect.objectContaining({
					method: 'POST',
					headers: { 'Content-Type': 'application/json' },
					body: expect.stringContaining('test@example.com'),
				})
			);

			// Verify tokens were stored
			expect(SecureStore.setItemAsync).toHaveBeenCalledWith('@auth/appToken', mockTokens.appToken);
			expect(SecureStore.setItemAsync).toHaveBeenCalledWith(
				'@auth/refreshToken',
				mockTokens.refreshToken
			);
		});

		it('should handle invalid credentials error', async () => {
			// Arrange
			const mockResponse = {
				ok: false,
				status: 401,
				json: async () => ({
					success: false,
					error: 'INVALID_CREDENTIALS',
				}),
			};

			(global.fetch as jest.Mock).mockResolvedValue(mockResponse);

			// Act
			const result = await authService.signIn('test@example.com', 'wrongpassword');

			// Assert
			expect(result.success).toBe(false);
			expect(result.error).toBe('INVALID_CREDENTIALS');
			expect(SecureStore.setItemAsync).not.toHaveBeenCalled();
		});

		it('should handle network errors', async () => {
			// Arrange
			(global.fetch as jest.Mock).mockRejectedValue(new Error('Network request failed'));

			// Act
			const result = await authService.signIn('test@example.com', 'password123');

			// Assert
			expect(result.success).toBe(false);
			expect(result.error).toContain('Network');
		});

		it('should handle storage errors', async () => {
			// Arrange
			const mockResponse = {
				ok: true,
				json: async () => ({
					success: true,
					...mockTokens,
					user: mockUser,
				}),
			};

			(global.fetch as jest.Mock).mockResolvedValue(mockResponse);
			(SecureStore.setItemAsync as jest.Mock).mockRejectedValue(new Error('Storage unavailable'));

			// Act
			const result = await authService.signIn('test@example.com', 'password123');

			// Assert
			expect(result.success).toBe(false);
			expect(result.error).toContain('Storage');
		});

		it('should validate email format', async () => {
			// Act
			const result = await authService.signIn('invalid-email', 'password123');

			// Assert
			expect(result.success).toBe(false);
			expect(result.error).toContain('email');
			expect(global.fetch).not.toHaveBeenCalled();
		});

		it('should validate password is not empty', async () => {
			// Act
			const result = await authService.signIn('test@example.com', '');

			// Assert
			expect(result.success).toBe(false);
			expect(result.error).toContain('password');
			expect(global.fetch).not.toHaveBeenCalled();
		});

		it('should handle timeout errors', async () => {
			jest.useFakeTimers();

			// Arrange
			(global.fetch as jest.Mock).mockImplementation(
				() =>
					new Promise((resolve) => {
						setTimeout(() => resolve({ ok: true, json: async () => ({}) }), 60000);
					})
			);

			// Act
			const resultPromise = authService.signIn('test@example.com', 'password123');

			jest.advanceTimersByTime(30000); // Advance 30s (timeout threshold)
			const result = await resultPromise;

			// Assert
			expect(result.success).toBe(false);
			expect(result.error).toContain('timeout');

			jest.useRealTimers();
		});
	});

	describe('signOut', () => {
		it('should sign out successfully', async () => {
			// Arrange
			(SecureStore.deleteItemAsync as jest.Mock).mockResolvedValue(undefined);
			(tokenManager.clearTokens as jest.Mock).mockResolvedValue(undefined);

			// Act
			const result = await authService.signOut();

			// Assert
			expect(result.success).toBe(true);
			expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('@auth/appToken');
			expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('@auth/refreshToken');
			expect(tokenManager.clearTokens).toHaveBeenCalled();
		});

		it('should handle storage errors during sign out', async () => {
			// Arrange
			(SecureStore.deleteItemAsync as jest.Mock).mockRejectedValue(new Error('Storage error'));

			// Act
			const result = await authService.signOut();

			// Assert
			// Should succeed even if storage fails (user intent matters)
			expect(result.success).toBe(true);
		});
	});

	describe('refreshToken', () => {
		it('should refresh token successfully', async () => {
			// Arrange
			const oldRefreshToken = 'old-refresh-token';
			const newTokens = {
				appToken: 'new-app-token',
				refreshToken: 'new-refresh-token',
			};

			(SecureStore.getItemAsync as jest.Mock).mockResolvedValue(oldRefreshToken);

			const mockResponse = {
				ok: true,
				json: async () => ({
					success: true,
					...newTokens,
				}),
			};

			(global.fetch as jest.Mock).mockResolvedValue(mockResponse);
			(SecureStore.setItemAsync as jest.Mock).mockResolvedValue(undefined);

			// Act
			const result = await authService.refreshToken();

			// Assert
			expect(result.success).toBe(true);
			expect(result.appToken).toBe(newTokens.appToken);
			expect(SecureStore.setItemAsync).toHaveBeenCalledWith('@auth/appToken', newTokens.appToken);
		});

		it('should handle missing refresh token', async () => {
			// Arrange
			(SecureStore.getItemAsync as jest.Mock).mockResolvedValue(null);

			// Act
			const result = await authService.refreshToken();

			// Assert
			expect(result.success).toBe(false);
			expect(result.error).toContain('No refresh token');
			expect(global.fetch).not.toHaveBeenCalled();
		});

		it('should handle expired refresh token', async () => {
			// Arrange
			(SecureStore.getItemAsync as jest.Mock).mockResolvedValue('expired-refresh-token');

			const mockResponse = {
				ok: false,
				status: 401,
				json: async () => ({
					success: false,
					error: 'REFRESH_TOKEN_EXPIRED',
				}),
			};

			(global.fetch as jest.Mock).mockResolvedValue(mockResponse);

			// Act
			const result = await authService.refreshToken();

			// Assert
			expect(result.success).toBe(false);
			expect(result.error).toBe('REFRESH_TOKEN_EXPIRED');
		});
	});

	describe('checkAuthStatus', () => {
		it('should return true when valid token exists', async () => {
			// Arrange
			(tokenManager.getValidToken as jest.Mock).mockResolvedValue('valid-token');

			// Act
			const result = await authService.checkAuthStatus();

			// Assert
			expect(result).toBe(true);
		});

		it('should return false when no token exists', async () => {
			// Arrange
			(tokenManager.getValidToken as jest.Mock).mockResolvedValue(null);

			// Act
			const result = await authService.checkAuthStatus();

			// Assert
			expect(result).toBe(false);
		});

		it('should refresh expired token automatically', async () => {
			// Arrange
			(tokenManager.getValidToken as jest.Mock)
				.mockResolvedValueOnce(null) // First call: no valid token
				.mockResolvedValueOnce('new-valid-token'); // After refresh

			(authService.refreshToken as jest.Mock) = jest.fn().mockResolvedValue({
				success: true,
				appToken: 'new-valid-token',
			});

			// Act
			const result = await authService.checkAuthStatus();

			// Assert
			expect(result).toBe(true);
			expect(authService.refreshToken).toHaveBeenCalled();
		});
	});

	describe('Integration with TokenManager', () => {
		it('should notify TokenManager of new tokens', async () => {
			// Arrange
			const mockResponse = {
				ok: true,
				json: async () => ({
					success: true,
					...mockTokens,
					user: mockUser,
				}),
			};

			(global.fetch as jest.Mock).mockResolvedValue(mockResponse);
			(SecureStore.setItemAsync as jest.Mock).mockResolvedValue(undefined);
			(tokenManager.setTokens as jest.Mock).mockResolvedValue(undefined);

			// Act
			await authService.signIn('test@example.com', 'password123');

			// Assert
			expect(tokenManager.setTokens).toHaveBeenCalledWith(mockTokens);
		});
	});
});
