import { AdminUser } from '../../domain/entities/AdminUser';
import { UserRole } from '../../domain/value-objects/UserRole';
import { UserStatus } from '../../domain/value-objects/UserStatus';
import { InMemoryAdminUserRepository } from './InMemoryAdminUserRepository';

describe('InMemoryAdminUserRepository', () => {
  describe('TDD - Test First', () => {
    let repository: InMemoryAdminUserRepository;

    beforeEach(() => {
      repository = new InMemoryAdminUserRepository();
    });

    describe('create', () => {
      it('should create a new user', async () => {
        // Arrange
        const user = AdminUser.create({
          id: 'user-123',
          email: 'test@example.com',
          displayName: 'Test User',
          roles: ['user'],
          status: 'active',
        });

        // Act
        const result = await repository.create(user);

        // Assert
        expect(result).toStrictEqual(user);
        const found = await repository.findById(user.id);
        expect(found).toStrictEqual(user);
      });

      it('should throw error when creating user with duplicate email', async () => {
        // Arrange
        const user1 = AdminUser.create({
          id: 'user-1',
          email: 'test@example.com',
          displayName: 'User 1',
          roles: ['user'],
          status: 'active',
        });

        const user2 = AdminUser.create({
          id: 'user-2',
          email: 'test@example.com',
          displayName: 'User 2',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user1);

        // Act & Assert
        await expect(repository.create(user2)).rejects.toThrow('Email already exists');
      });

      it('should throw error when creating user with duplicate ID', async () => {
        // Arrange
        const user1 = AdminUser.create({
          id: 'user-1',
          email: 'user1@example.com',
          displayName: 'User 1',
          roles: ['user'],
          status: 'active',
        });

        const user2 = AdminUser.create({
          id: 'user-1',
          email: 'user2@example.com',
          displayName: 'User 2',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user1);

        // Act & Assert
        await expect(repository.create(user2)).rejects.toThrow('User ID already exists');
      });
    });

    describe('findById', () => {
      it('should find user by ID', async () => {
        // Arrange
        const user = AdminUser.create({
          id: 'user-123',
          email: 'test@example.com',
          displayName: 'Test User',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user);

        // Act
        const found = await repository.findById(user.id);

        // Assert
        expect(found).toStrictEqual(user);
      });

      it('should return null for non-existent user', async () => {
        // Arrange
        const nonExistentId = AdminUser.create({
          id: 'non-existent',
          email: 'dummy@example.com',
          displayName: 'Dummy',
          roles: ['user'],
          status: 'active',
        }).id;

        // Act
        const found = await repository.findById(nonExistentId);

        // Assert
        expect(found).toBeNull();
      });
    });

    describe('findByEmail', () => {
      it('should find user by email', async () => {
        // Arrange
        const user = AdminUser.create({
          id: 'user-123',
          email: 'test@example.com',
          displayName: 'Test User',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user);

        // Act
        const found = await repository.findByEmail(user.email);

        // Assert
        expect(found).toStrictEqual(user);
      });

      it('should return null for non-existent email', async () => {
        // Arrange
        const nonExistentEmail = AdminUser.create({
          id: 'dummy',
          email: 'non-existent@example.com',
          displayName: 'Dummy',
          roles: ['user'],
          status: 'active',
        }).email;

        // Act
        const found = await repository.findByEmail(nonExistentEmail);

        // Assert
        expect(found).toBeNull();
      });
    });

    describe('emailExists', () => {
      it('should return true when email exists', async () => {
        // Arrange
        const user = AdminUser.create({
          id: 'user-123',
          email: 'test@example.com',
          displayName: 'Test User',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user);

        // Act
        const exists = await repository.emailExists(user.email);

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

      it('should return false when email does not exist', async () => {
        // Arrange
        const nonExistentEmail = AdminUser.create({
          id: 'dummy',
          email: 'non-existent@example.com',
          displayName: 'Dummy',
          roles: ['user'],
          status: 'active',
        }).email;

        // Act
        const exists = await repository.emailExists(nonExistentEmail);

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

    describe('list', () => {
      it('should return empty list when no users exist', async () => {
        // Act
        const result = await repository.list({});

        // Assert
        expect(result.users).toEqual([]);
        expect(result.total).toBe(0);
        expect(result.page).toBe(1);
        expect(result.limit).toBe(10);
        expect(result.totalPages).toBe(0);
      });

      it('should return all users when no filters provided', async () => {
        // Arrange
        const user1 = AdminUser.create({
          id: 'user-1',
          email: 'user1@example.com',
          displayName: 'User 1',
          roles: ['user'],
          status: 'active',
        });

        const user2 = AdminUser.create({
          id: 'user-2',
          email: 'user2@example.com',
          displayName: 'User 2',
          roles: ['admin'],
          status: 'active',
        });

        await repository.create(user1);
        await repository.create(user2);

        // Act
        const result = await repository.list({});

        // Assert
        expect(result.users).toHaveLength(2);
        expect(result.total).toBe(2);
        expect(result.page).toBe(1);
        expect(result.limit).toBe(10);
        expect(result.totalPages).toBe(1);
      });

      it('should filter by role', async () => {
        // Arrange
        const user1 = AdminUser.create({
          id: 'user-1',
          email: 'user1@example.com',
          displayName: 'User 1',
          roles: ['user'],
          status: 'active',
        });

        const admin1 = AdminUser.create({
          id: 'admin-1',
          email: 'admin1@example.com',
          displayName: 'Admin 1',
          roles: ['admin'],
          status: 'active',
        });

        await repository.create(user1);
        await repository.create(admin1);

        // Act
        const result = await repository.list({
          filter: { role: UserRole.fromString('admin') },
        });

        // Assert
        expect(result.users).toHaveLength(1);
        expect(result.users[0]?.id.value).toBe('admin-1');
        expect(result.total).toBe(1);
      });

      it('should filter by status', async () => {
        // Arrange
        const activeUser = AdminUser.create({
          id: 'user-1',
          email: 'active@example.com',
          displayName: 'Active User',
          roles: ['user'],
          status: 'active',
        });

        const suspendedUser = AdminUser.create({
          id: 'user-2',
          email: 'suspended@example.com',
          displayName: 'Suspended User',
          roles: ['user'],
          status: 'suspended',
        });

        await repository.create(activeUser);
        await repository.create(suspendedUser);

        // Act
        const result = await repository.list({
          filter: { status: UserStatus.fromString('suspended') },
        });

        // Assert
        expect(result.users).toHaveLength(1);
        expect(result.users[0]?.id.value).toBe('user-2');
        expect(result.total).toBe(1);
      });

      it('should filter by email', async () => {
        // Arrange
        const user1 = AdminUser.create({
          id: 'user-1',
          email: 'test@example.com',
          displayName: 'Test User',
          roles: ['user'],
          status: 'active',
        });

        const user2 = AdminUser.create({
          id: 'user-2',
          email: 'other@example.com',
          displayName: 'Other User',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user1);
        await repository.create(user2);

        // Act
        const result = await repository.list({
          filter: { email: user1.email },
        });

        // Assert
        expect(result.users).toHaveLength(1);
        expect(result.users[0]?.id.value).toBe('user-1');
        expect(result.total).toBe(1);
      });

      it('should filter by search (email or display name)', async () => {
        // Arrange
        const user1 = AdminUser.create({
          id: 'user-1',
          email: 'search@example.com',
          displayName: 'Search User',
          roles: ['user'],
          status: 'active',
        });

        const user2 = AdminUser.create({
          id: 'user-2',
          email: 'other@example.com',
          displayName: 'Other User',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user1);
        await repository.create(user2);

        // Act
        const result = await repository.list({
          filter: { search: 'search' },
        });

        // Assert
        expect(result.users).toHaveLength(1);
        expect(result.users[0]?.id.value).toBe('user-1');
        expect(result.total).toBe(1);
      });

      it('should apply pagination', async () => {
        // Arrange - Create 15 users
        for (let i = 1; i <= 15; i++) {
          const user = AdminUser.create({
            id: `user-${i}`,
            email: `user${i}@example.com`,
            displayName: `User ${i}`,
            roles: ['user'],
            status: 'active',
          });
          await repository.create(user);
        }

        // Act - Get page 2 with limit 5
        const result = await repository.list({
          pagination: { page: 2, limit: 5 },
        });

        // Assert
        expect(result.users).toHaveLength(5);
        expect(result.total).toBe(15);
        expect(result.page).toBe(2);
        expect(result.limit).toBe(5);
        expect(result.totalPages).toBe(3);
        expect(result.users[0]?.id.value).toBe('user-6');
      });

      it('should sort by email ascending', async () => {
        // Arrange
        const user1 = AdminUser.create({
          id: 'user-1',
          email: 'zebra@example.com',
          displayName: 'Zebra',
          roles: ['user'],
          status: 'active',
        });

        const user2 = AdminUser.create({
          id: 'user-2',
          email: 'alpha@example.com',
          displayName: 'Alpha',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user1);
        await repository.create(user2);

        // Act
        const result = await repository.list({
          sort: { field: 'email', direction: 'asc' },
        });

        // Assert
        expect(result.users).toHaveLength(2);
        expect(result.users[0]?.id.value).toBe('user-2');
        expect(result.users[1]?.id.value).toBe('user-1');
      });

      it('should sort by display name descending', async () => {
        // Arrange
        const user1 = AdminUser.create({
          id: 'user-1',
          email: 'user1@example.com',
          displayName: 'Zebra',
          roles: ['user'],
          status: 'active',
        });

        const user2 = AdminUser.create({
          id: 'user-2',
          email: 'user2@example.com',
          displayName: 'Alpha',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user1);
        await repository.create(user2);

        // Act
        const result = await repository.list({
          sort: { field: 'displayName', direction: 'desc' },
        });

        // Assert
        expect(result.users).toHaveLength(2);
        expect(result.users[0]?.id.value).toBe('user-1');
        expect(result.users[1]?.id.value).toBe('user-2');
      });

      it('should apply multiple filters together', async () => {
        // Arrange
        const matchingUser = AdminUser.create({
          id: 'user-1',
          email: 'admin@example.com',
          displayName: 'Admin User',
          roles: ['admin'],
          status: 'active',
        });

        const nonMatchingUser1 = AdminUser.create({
          id: 'user-2',
          email: 'user@example.com',
          displayName: 'User',
          roles: ['user'],
          status: 'active',
        });

        const nonMatchingUser2 = AdminUser.create({
          id: 'user-3',
          email: 'admin-suspended@example.com',
          displayName: 'Admin User',
          roles: ['admin'],
          status: 'suspended',
        });

        await repository.create(matchingUser);
        await repository.create(nonMatchingUser1);
        await repository.create(nonMatchingUser2);

        // Act
        const result = await repository.list({
          filter: {
            role: UserRole.fromString('admin'),
            status: UserStatus.fromString('active'),
            search: 'admin',
          },
        });

        // Assert
        expect(result.users).toHaveLength(1);
        expect(result.users[0]?.id.value).toBe('user-1');
        expect(result.total).toBe(1);
      });
    });

    describe('count', () => {
      it('should return 0 when no users exist', async () => {
        // Act
        const count = await repository.count();

        // Assert
        expect(count).toBe(0);
      });

      it('should return correct count of all users', async () => {
        // Arrange
        const user1 = AdminUser.create({
          id: 'user-1',
          email: 'user1@example.com',
          displayName: 'User 1',
          roles: ['user'],
          status: 'active',
        });

        const user2 = AdminUser.create({
          id: 'user-2',
          email: 'user2@example.com',
          displayName: 'User 2',
          roles: ['admin'],
          status: 'active',
        });

        await repository.create(user1);
        await repository.create(user2);

        // Act
        const count = await repository.count();

        // Assert
        expect(count).toBe(2);
      });

      it('should count with filters', async () => {
        // Arrange
        const activeUser = AdminUser.create({
          id: 'user-1',
          email: 'active@example.com',
          displayName: 'Active User',
          roles: ['user'],
          status: 'active',
        });

        const suspendedUser = AdminUser.create({
          id: 'user-2',
          email: 'suspended@example.com',
          displayName: 'Suspended User',
          roles: ['user'],
          status: 'suspended',
        });

        await repository.create(activeUser);
        await repository.create(suspendedUser);

        // Act
        const count = await repository.count({
          status: UserStatus.fromString('active'),
        });

        // Assert
        expect(count).toBe(1);
      });
    });

    describe('update', () => {
      it('should update existing user', async () => {
        // Arrange
        const user = AdminUser.create({
          id: 'user-1',
          email: 'test@example.com',
          displayName: 'Test User',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user);

        // Act
        user.updateDisplayName('Updated Name');
        const updated = await repository.update(user);

        // Assert
        expect(updated.displayName).toBe('Updated Name');
        const found = await repository.findById(user.id);
        expect(found?.displayName).toBe('Updated Name');
      });

      it('should throw error when updating non-existent user', async () => {
        // Arrange
        const user = AdminUser.create({
          id: 'non-existent',
          email: 'test@example.com',
          displayName: 'Test User',
          roles: ['user'],
          status: 'active',
        });

        // Act & Assert
        await expect(repository.update(user)).rejects.toThrow('User not found');
      });

      it('should throw error when updating with duplicate email', async () => {
        // Arrange
        const user1 = AdminUser.create({
          id: 'user-1',
          email: 'user1@example.com',
          displayName: 'User 1',
          roles: ['user'],
          status: 'active',
        });

        const user2 = AdminUser.create({
          id: 'user-2',
          email: 'user2@example.com',
          displayName: 'User 2',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user1);
        await repository.create(user2);

        // Act - Try to change user2's email to user1's email
        user2.updateEmail(user1.email);

        // Assert
        await expect(repository.update(user2)).rejects.toThrow('Email already exists');
      });
    });

    describe('delete', () => {
      it('should delete existing user', async () => {
        // Arrange
        const user = AdminUser.create({
          id: 'user-1',
          email: 'test@example.com',
          displayName: 'Test User',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user);

        // Act
        await repository.delete(user.id);

        // Assert
        const found = await repository.findById(user.id);
        expect(found).toBeNull();
      });

      it('should throw error when deleting non-existent user', async () => {
        // Arrange
        const nonExistentId = AdminUser.create({
          id: 'non-existent',
          email: 'test@example.com',
          displayName: 'Test User',
          roles: ['user'],
          status: 'active',
        }).id;

        // Act & Assert
        await expect(repository.delete(nonExistentId)).rejects.toThrow('User not found');
      });

      it('should reduce count after deletion', async () => {
        // Arrange
        const user = AdminUser.create({
          id: 'user-1',
          email: 'test@example.com',
          displayName: 'Test User',
          roles: ['user'],
          status: 'active',
        });

        await repository.create(user);
        const initialCount = await repository.count();

        // Act
        await repository.delete(user.id);

        // Assert
        const finalCount = await repository.count();
        expect(finalCount).toBe(initialCount - 1);
      });
    });

    describe('integration scenarios', () => {
      it('should handle complete user lifecycle', async () => {
        // Arrange - Create user
        const user = AdminUser.create({
          id: 'user-1',
          email: 'lifecycle@example.com',
          displayName: 'Lifecycle User',
          roles: ['user'],
          status: 'active',
        });

        // Act - Create
        await repository.create(user);
        let found = await repository.findById(user.id);
        expect(found).toStrictEqual(user);

        // Act - Update
        user.updateDisplayName('Updated Lifecycle');
        user.addRole(UserRole.fromString('admin'));
        await repository.update(user);
        found = await repository.findById(user.id);
        expect(found?.displayName).toBe('Updated Lifecycle');
        expect(found?.hasRole('admin')).toBe(true);

        // Act - Delete
        await repository.delete(user.id);
        found = await repository.findById(user.id);
        expect(found).toBeNull();
      });

      it('should handle complex filtering and pagination', async () => {
        // Arrange - Create mixed users
        const users = [
          AdminUser.create({
            id: 'owner-1',
            email: 'owner@example.com',
            displayName: 'Owner User',
            roles: ['owner'],
            status: 'active',
          }),
          AdminUser.create({
            id: 'admin-1',
            email: 'admin1@example.com',
            displayName: 'Admin One',
            roles: ['admin'],
            status: 'active',
          }),
          AdminUser.create({
            id: 'admin-2',
            email: 'admin2@example.com',
            displayName: 'Admin Two',
            roles: ['admin'],
            status: 'suspended',
          }),
          AdminUser.create({
            id: 'user-1',
            email: 'user1@example.com',
            displayName: 'User One',
            roles: ['user'],
            status: 'active',
          }),
          AdminUser.create({
            id: 'user-2',
            email: 'user2@example.com',
            displayName: 'User Two',
            roles: ['user'],
            status: 'active',
          }),
        ];

        for (const user of users) {
          await repository.create(user);
        }

        // Act - Get active admins, sorted by email, page 1, limit 2
        const result = await repository.list({
          filter: {
            role: UserRole.fromString('admin'),
            status: UserStatus.fromString('active'),
          },
          sort: { field: 'email', direction: 'asc' },
          pagination: { page: 1, limit: 2 },
        });

        // Assert
        expect(result.users).toHaveLength(1);
        expect(result.users[0]?.id.value).toBe('admin-1');
        expect(result.total).toBe(1);
        expect(result.totalPages).toBe(1);
      });
    });
  });
});