import { describe, it, expect, vi, Mocked, beforeEach } from 'vitest';
import { SessionService } from '@/lib/services/auth/SessionService';
import { AuthApiClient } from '@/lib/api/auth/AuthApiClient';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';

// Mock dependencies
vi.mock('@/lib/config/apiBaseUrl', () => ({
  getWebsiteApiBaseUrl: () => 'http://localhost:3000',
}));
vi.mock('@/lib/config/env', () => ({
  isProductionEnvironment: () => false,
}));

describe('SessionService', () => {
  let mockApiClient: Mocked<AuthApiClient>;
  let service: SessionService;

  beforeEach(() => {
    mockApiClient = {
      signup: vi.fn(),
      login: vi.fn(),
      logout: vi.fn(),
      forgotPassword: vi.fn(),
      resetPassword: vi.fn(),
      getSession: vi.fn(),
    } as Mocked<AuthApiClient>;

    service = new SessionService(mockApiClient);
  });

  describe('getSession', () => {
    describe('happy paths', () => {
      it('should call apiClient.getSession and return SessionViewModel when session exists', async () => {
        const mockResponse = {
          token: 'jwt-token',
          user: {
            userId: 'user-123',
            email: 'test@example.com',
            displayName: 'Test User',
          },
        };

        mockApiClient.getSession.mockResolvedValue(mockResponse);

        const result = await service.getSession();

        expect(mockApiClient.getSession).toHaveBeenCalled();
        expect(result.isOk()).toBe(true);
        const vm = result.unwrap();
        expect(vm).toBeInstanceOf(SessionViewModel);
        expect(vm?.userId).toBe('user-123');
        expect(vm?.email).toBe('test@example.com');
        expect(vm?.displayName).toBe('Test User');
        expect(vm?.isAuthenticated).toBe(true);
      });

      it('should return null when apiClient.getSession returns null', async () => {
        mockApiClient.getSession.mockResolvedValue(null);

        const result = await service.getSession();

        expect(mockApiClient.getSession).toHaveBeenCalled();
        expect(result.isOk()).toBe(true);
        expect(result.unwrap()).toBeNull();
      });

      it('should return null when apiClient.getSession returns undefined', async () => {
        mockApiClient.getSession.mockResolvedValue(undefined);

        const result = await service.getSession();

        expect(mockApiClient.getSession).toHaveBeenCalled();
        expect(result.isOk()).toBe(true);
        expect(result.unwrap()).toBeNull();
      });

      it('should return null when session has no user data', async () => {
        const mockResponse = {
          token: 'jwt-token',
          user: null,
        };

        mockApiClient.getSession.mockResolvedValue(mockResponse);

        const result = await service.getSession();

        expect(mockApiClient.getSession).toHaveBeenCalled();
        expect(result.isOk()).toBe(true);
        expect(result.unwrap()).toBeNull();
      });
    });

    describe('failure modes', () => {
      it('should handle server errors', async () => {
        const error = new Error('Get session failed');
        mockApiClient.getSession.mockRejectedValue(error);

        const result = await service.getSession();
        expect(result.isErr()).toBe(true);
        expect(result.getError().type).toBe('serverError');
        expect(result.getError().message).toBe('Get session failed');
      });

      it('should handle network errors', async () => {
        const error = new Error('Network error');
        mockApiClient.getSession.mockRejectedValue(error);

        const result = await service.getSession();
        expect(result.isErr()).toBe(true);
        expect(result.getError().type).toBe('serverError');
        expect(result.getError().message).toBe('Network error');
      });

      it('should handle authentication errors', async () => {
        const error = new Error('Invalid token');
        mockApiClient.getSession.mockRejectedValue(error);

        const result = await service.getSession();
        expect(result.isErr()).toBe(true);
        expect(result.getError().type).toBe('serverError');
        expect(result.getError().message).toBe('Invalid token');
      });

      it('should handle timeout errors', async () => {
        const error = new Error('Request timeout');
        mockApiClient.getSession.mockRejectedValue(error);

        const result = await service.getSession();
        expect(result.isErr()).toBe(true);
        expect(result.getError().type).toBe('serverError');
        expect(result.getError().message).toBe('Request timeout');
      });
    });

    describe('decision branches', () => {
      it('should handle different user data structures', async () => {
        const mockResponse = {
          token: 'jwt-token',
          user: {
            userId: 'user-123',
            email: 'test@example.com',
            displayName: 'Test User',
            avatarUrl: 'https://example.com/avatar.jpg',
            role: 'admin',
            permissions: ['read', 'write'],
            lastLogin: '2024-01-01T00:00:00.000Z',
            createdAt: '2023-01-01T00:00:00.000Z',
            updatedAt: '2024-01-01T00:00:00.000Z',
          },
        };

        mockApiClient.getSession.mockResolvedValue(mockResponse);

        const result = await service.getSession();
        expect(result.isOk()).toBe(true);
        const vm = result.unwrap();
        expect(vm).toBeInstanceOf(SessionViewModel);
        expect(vm?.userId).toBe('user-123');
        expect(vm?.email).toBe('test@example.com');
        expect(vm?.displayName).toBe('Test User');
        expect(vm?.isAuthenticated).toBe(true);
      });

      it('should handle user with minimal data', async () => {
        const mockResponse = {
          token: 'jwt-token',
          user: {
            userId: 'user-123',
            email: 'test@example.com',
            displayName: '',
          },
        };

        mockApiClient.getSession.mockResolvedValue(mockResponse);

        const result = await service.getSession();
        expect(result.isOk()).toBe(true);
        const vm = result.unwrap();
        expect(vm?.displayName).toBe('');
        expect(vm?.isAuthenticated).toBe(true);
      });

      it('should handle user with special characters in display name', async () => {
        const mockResponse = {
          token: 'jwt-token',
          user: {
            userId: 'user-123',
            email: 'test@example.com',
            displayName: 'Test User <script>alert("xss")</script>',
          },
        };

        mockApiClient.getSession.mockResolvedValue(mockResponse);

        const result = await service.getSession();
        expect(result.isOk()).toBe(true);
        const vm = result.unwrap();
        expect(vm?.displayName).toBe('Test User <script>alert("xss")</script>');
        expect(vm?.isAuthenticated).toBe(true);
      });

      it('should handle different email formats', async () => {
        const emails = [
          'user@example.com',
          'user+tag@example.com',
          'user.name@example.com',
          'user@subdomain.example.com',
        ];

        for (const email of emails) {
          const mockResponse = {
            token: 'jwt-token',
            user: {
              userId: 'user-123',
              email,
              displayName: 'Test User',
            },
          };

          mockApiClient.getSession.mockResolvedValue(mockResponse);

          const result = await service.getSession();
          expect(result.isOk()).toBe(true);
          const vm = result.unwrap();
          expect(vm?.email).toBe(email);
        }
      });

      it('should handle different token formats', async () => {
        const tokens = [
          'simple-token',
          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
          'token-with-special-chars-!@#$%^&*()',
        ];

        for (const token of tokens) {
          const mockResponse = {
            token,
            user: {
              userId: 'user-123',
              email: 'test@example.com',
              displayName: 'Test User',
            },
          };

          mockApiClient.getSession.mockResolvedValue(mockResponse);

          const result = await service.getSession();
          expect(result.isOk()).toBe(true);
          const vm = result.unwrap();
          expect(vm?.isAuthenticated).toBe(true);
        }
      });
    });

    describe('aggregation logic', () => {
      it('should aggregate session data correctly', async () => {
        const mockResponse = {
          token: 'jwt-token',
          user: {
            userId: 'user-123',
            email: 'test@example.com',
            displayName: 'Test User',
          },
        };

        mockApiClient.getSession.mockResolvedValue(mockResponse);

        const result = await service.getSession();

        expect(result.isOk()).toBe(true);
        const vm = result.unwrap();

        // Verify all user data is aggregated into the view model
        expect(vm?.userId).toBe('user-123');
        expect(vm?.email).toBe('test@example.com');
        expect(vm?.displayName).toBe('Test User');
        expect(vm?.isAuthenticated).toBe(true);
      });

      it('should handle empty user object', async () => {
        const mockResponse = {
          token: 'jwt-token',
          user: {},
        };

        mockApiClient.getSession.mockResolvedValue(mockResponse);

        const result = await service.getSession();

        expect(result.isOk()).toBe(true);
        expect(result.unwrap()).toBeNull();
      });

      it('should handle missing token', async () => {
        const mockResponse = {
          token: null,
          user: {
            userId: 'user-123',
            email: 'test@example.com',
            displayName: 'Test User',
          },
        };

        mockApiClient.getSession.mockResolvedValue(mockResponse);

        const result = await service.getSession();

        expect(result.isOk()).toBe(true);
        const vm = result.unwrap();
        expect(vm).toBeInstanceOf(SessionViewModel);
        expect(vm?.userId).toBe('user-123');
      });
    });
  });

  describe('error handling', () => {
    it('should handle unexpected error types', async () => {
      const error = { customError: 'Something went wrong' };
      mockApiClient.getSession.mockRejectedValue(error);

      const result = await service.getSession();
      expect(result.isErr()).toBe(true);
      expect(result.getError().type).toBe('serverError');
      expect(result.getError().message).toBe('Something went wrong');
    });

    it('should handle string errors', async () => {
      mockApiClient.getSession.mockRejectedValue('String error');

      const result = await service.getSession();
      expect(result.isErr()).toBe(true);
      expect(result.getError().type).toBe('serverError');
      expect(result.getError().message).toBe('String error');
    });

    it('should handle undefined errors', async () => {
      mockApiClient.getSession.mockRejectedValue(undefined);

      const result = await service.getSession();
      expect(result.isErr()).toBe(true);
      expect(result.getError().type).toBe('serverError');
      expect(result.getError().message).toBe('Failed to get session');
    });
  });
});
