import { beforeEach, describe, expect, it, vi } from 'vitest';
import { AdminUser } from '../../domain/entities/AdminUser';
import { AuthorizationService } from '../../domain/services/AuthorizationService';
import { AdminUserRepository } from '../ports/AdminUserRepository';
import { ListUsersUseCase } from './ListUsersUseCase';

// Mock the authorization service
vi.mock('../../domain/services/AuthorizationService');

// Mock repository
const mockRepository = {
  findById: vi.fn(),
  findByEmail: vi.fn(),
  emailExists: vi.fn(),
  list: vi.fn(),
  count: vi.fn(),
  create: vi.fn(),
  update: vi.fn(),
  delete: vi.fn(),
} as unknown as AdminUserRepository;

describe('ListUsersUseCase', () => {
  let useCase: ListUsersUseCase;
  let actor: AdminUser;

  beforeEach(() => {
    vi.clearAllMocks();
    
    // Reset all mocks
    vi.mocked(mockRepository.findById).mockReset();
    vi.mocked(mockRepository.list).mockReset();
    vi.mocked(mockRepository.count).mockReset();
    vi.mocked(AuthorizationService.canListUsers).mockReset();
    
    // Setup default successful authorization
    vi.mocked(AuthorizationService.canListUsers).mockReturnValue(true);
    
    useCase = new ListUsersUseCase(mockRepository);
    
    // Create actor (owner)
    actor = AdminUser.create({
      id: 'actor-123',
      email: 'owner@example.com',
      roles: ['owner'],
      status: 'active',
      displayName: 'Owner User',
    });
    
    // Setup repository to return actor when findById is called
    vi.mocked(mockRepository.findById).mockResolvedValue(actor);
  });

  describe('TDD - Test First', () => {
    it('should return empty list when no users exist', async () => {
      // Arrange
      vi.mocked(mockRepository.list).mockResolvedValue({
        users: [],
        total: 0,
        page: 1,
        limit: 10,
        totalPages: 0,
      });

      vi.mocked(mockRepository.count).mockResolvedValue(0);

      // Act
      const result = await useCase.execute({
        actorId: actor.id.value,
      });

      // Assert
      expect(result.isOk()).toBe(true);
      const data = result.unwrap();
      expect(data).toEqual({
        users: [],
        total: 0,
        page: 1,
        limit: 10,
        totalPages: 0,
      });
    });

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

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

      vi.mocked(mockRepository.list).mockResolvedValue({
        users: [user1, user2],
        total: 2,
        page: 1,
        limit: 10,
        totalPages: 1,
      });

      vi.mocked(mockRepository.count).mockResolvedValue(2);

      // Act
      const result = await useCase.execute({
        actorId: actor.id.value,
      });

      // Assert
      expect(result.isOk()).toBe(true);
      const data = result.unwrap();
      expect(data.users).toEqual([user1, user2]);
      expect(data.total).toBe(2);
      expect(data.page).toBe(1);
      expect(data.limit).toBe(10);
      expect(data.totalPages).toBe(1);
    });

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

      vi.mocked(mockRepository.list).mockResolvedValue({
        users: [adminUser],
        total: 1,
        page: 1,
        limit: 10,
        totalPages: 1,
      });

      vi.mocked(mockRepository.count).mockResolvedValue(1);

      // Act
      const result = await useCase.execute({
        actorId: actor.id.value,
        role: 'admin',
      });

      // Assert
      expect(result.isOk()).toBe(true);
      expect(mockRepository.list).toHaveBeenCalledWith(
        expect.objectContaining({
          filter: expect.objectContaining({
            role: expect.objectContaining({ value: 'admin' }),
          }),
        })
      );
    });

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

      vi.mocked(mockRepository.list).mockResolvedValue({
        users: [suspendedUser],
        total: 1,
        page: 1,
        limit: 10,
        totalPages: 1,
      });

      vi.mocked(mockRepository.count).mockResolvedValue(1);

      // Act
      const result = await useCase.execute({
        actorId: actor.id.value,
        status: 'suspended',
      });

      // Assert
      expect(result.isOk()).toBe(true);
      expect(mockRepository.list).toHaveBeenCalledWith(
        expect.objectContaining({
          filter: expect.objectContaining({
            status: expect.objectContaining({ value: 'suspended' }),
          }),
        })
      );
    });

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

      vi.mocked(mockRepository.list).mockResolvedValue({
        users: [matchingUser],
        total: 1,
        page: 1,
        limit: 10,
        totalPages: 1,
      });

      vi.mocked(mockRepository.count).mockResolvedValue(1);

      // Act
      const result = await useCase.execute({
        actorId: actor.id.value,
        search: 'search',
      });

      // Assert
      expect(result.isOk()).toBe(true);
      expect(mockRepository.list).toHaveBeenCalledWith(
        expect.objectContaining({
          filter: expect.objectContaining({
            search: 'search',
          }),
        })
      );
    });

    it('should paginate results', async () => {
      // Arrange
      const users = Array.from({ length: 5 }, (_, i) =>
        AdminUser.create({
          id: `user-${i}`,
          email: `user${i}@example.com`,
          roles: ['user'],
          status: 'active',
          displayName: `User ${i}`,
        })
      );

      vi.mocked(mockRepository.list).mockResolvedValue({
        users: users.slice(0, 2),
        total: 5,
        page: 1,
        limit: 2,
        totalPages: 3,
      });

      vi.mocked(mockRepository.count).mockResolvedValue(5);

      // Act
      const result = await useCase.execute({
        actorId: actor.id.value,
        page: 1,
        limit: 2,
      });

      // Assert
      expect(result.isOk()).toBe(true);
      expect(mockRepository.list).toHaveBeenCalledWith(
        expect.objectContaining({
          pagination: { page: 1, limit: 2 },
        })
      );
    });

    it('should sort results', async () => {
      // Arrange
      vi.mocked(mockRepository.list).mockResolvedValue({
        users: [],
        total: 0,
        page: 1,
        limit: 10,
        totalPages: 0,
      });

      vi.mocked(mockRepository.count).mockResolvedValue(0);

      // Act
      const result = await useCase.execute({
        actorId: actor.id.value,
        sortBy: 'email',
        sortDirection: 'desc',
      });

      // Assert
      expect(result.isOk()).toBe(true);
      expect(mockRepository.list).toHaveBeenCalledWith(
        expect.objectContaining({
          sort: {
            field: 'email',
            direction: 'desc',
          },
        })
      );
    });

    it('should return error when actor is not authorized', async () => {
      // Arrange
      const regularUser = AdminUser.create({
        id: 'regular-1',
        email: 'regular@example.com',
        roles: ['user'],
        status: 'active',
        displayName: 'Regular User',
      });

      // Mock authorization to fail
      vi.mocked(AuthorizationService.canListUsers).mockReturnValue(false);

      // Act
      const result = await useCase.execute({
        actorId: regularUser.id.value,
      });

      // Assert
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('AUTHORIZATION_ERROR');
      expect(error.details.message).toContain('not authorized');
    });

    it('should handle repository errors gracefully', async () => {
      // Arrange
      vi.mocked(mockRepository.list).mockRejectedValue(new Error('Database connection failed'));

      // Act
      const result = await useCase.execute({
        actorId: actor.id.value,
      });

      // Assert
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('REPOSITORY_ERROR');
      expect(error.details.message).toContain('Database connection failed');
    });

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

      vi.mocked(mockRepository.list).mockResolvedValue({
        users: [matchingUser],
        total: 1,
        page: 1,
        limit: 10,
        totalPages: 1,
      });

      vi.mocked(mockRepository.count).mockResolvedValue(1);

      // Act
      const result = await useCase.execute({
        actorId: actor.id.value,
        role: 'admin',
        status: 'active',
        search: 'admin',
      });

      // Assert
      expect(result.isOk()).toBe(true);
      expect(mockRepository.list).toHaveBeenCalledWith(
        expect.objectContaining({
          filter: expect.objectContaining({
            role: expect.objectContaining({ value: 'admin' }),
            status: expect.objectContaining({ value: 'active' }),
            search: 'admin',
          }),
        })
      );
    });
  });
});