import { GUARDS_METADATA, PATH_METADATA } from '@nestjs/common/constants';
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';
import { RoleEnum } from '../roles/roles.enum';
import { RolesGuard } from '../roles/roles.guard';
import { AdminUsersController } from './admin-users.controller';
import {
  AdminPlayerQueryDto,
  AdminUserProfileDto,
  AdminUserRoleDto,
  AdminUserStatusDto,
} from './dto/admin-user.dto';

describe('AdminUsersController', () => {
  const service = {
    updateProfile: jest.fn(),
    updateRole: jest.fn(),
    updateStatus: jest.fn(),
    findPlayers: jest.fn(),
    assignPlayer: jest.fn(),
    unlinkPlayer: jest.fn(),
  };
  const controller = new AdminUsersController(service as any);
  const actor = { user: { id: 7, role: { id: RoleEnum.admin } } };

  beforeEach(() => jest.clearAllMocks());

  it('uses a separate versioned admin/users controller guarded by the global admin role', () => {
    expect(Reflect.getMetadata(PATH_METADATA, AdminUsersController)).toBe(
      'admin/users',
    );
    expect(Reflect.getMetadata('roles', AdminUsersController)).toEqual([
      RoleEnum.admin,
    ]);
    expect(
      Reflect.getMetadata(GUARDS_METADATA, AdminUsersController),
    ).toContain(RolesGuard);
  });

  it('passes the authenticated actor and target to profile, role, and status mutations', async () => {
    await controller.updateProfile(actor as any, 2, {
      firstName: 'New',
      lastName: 'Name',
    });
    await controller.updateRole(actor as any, 2, { role: RoleEnum.user });
    await controller.updateStatus(actor as any, 2, {
      status: 2,
    });

    expect(service.updateProfile).toHaveBeenCalledWith(7, 2, {
      firstName: 'New',
      lastName: 'Name',
    });
    expect(service.updateRole).toHaveBeenCalledWith(7, 2, RoleEnum.user);
    expect(service.updateStatus).toHaveBeenCalledWith(7, 2, 2);
  });

  it('passes player search and atomic assignment operations to the service', async () => {
    const query = { assignment: 'unassigned' as const, page: 2, limit: 10 };

    await controller.findPlayers(query);
    await controller.assignPlayer(actor as any, 3, 11);
    await controller.unlinkPlayer(actor as any, 3, 11);

    expect(service.findPlayers).toHaveBeenCalledWith(query);
    expect(service.assignPlayer).toHaveBeenCalledWith(7, 3, 11);
    expect(service.unlinkPlayer).toHaveBeenCalledWith(7, 3, 11);
  });
});

describe('admin user DTOs', () => {
  it('accepts omitted and string profile names but rejects explicit nulls', async () => {
    const omitted = plainToInstance(AdminUserProfileDto, {});
    const names = plainToInstance(AdminUserProfileDto, {
      firstName: 'Ada',
      lastName: 'Admin',
    });
    const nulls = plainToInstance(AdminUserProfileDto, {
      firstName: null,
      lastName: null,
    });

    expect(await validate(omitted)).toEqual([]);
    expect(await validate(names)).toEqual([]);
    expect(await validate(nulls)).not.toEqual([]);
  });

  it('accepts only profile names in the profile DTO', async () => {
    const dto = plainToInstance(AdminUserProfileDto, {
      firstName: 'Ada',
      lastName: 'Admin',
      email: 'must-not-pass@example.com',
      role: RoleEnum.user,
      status: 2,
    });

    expect(await validate(dto, { whitelist: true })).toEqual([]);
    expect(dto).toEqual({ firstName: 'Ada', lastName: 'Admin' });
  });

  it('rejects role and status ids outside their enums', async () => {
    const role = plainToInstance(AdminUserRoleDto, { role: 999 });
    const status = plainToInstance(AdminUserStatusDto, { status: 999 });

    expect(await validate(role)).not.toEqual([]);
    expect(await validate(status)).not.toEqual([]);
  });

  it('rejects numeric-enum reverse-map names instead of sending strings to integer foreign keys', async () => {
    const role = plainToInstance(AdminUserRoleDto, { role: 'admin' });
    const status = plainToInstance(AdminUserStatusDto, { status: 'inactive' });

    expect(await validate(role)).not.toEqual([]);
    expect(await validate(status)).not.toEqual([]);
  });

  it.each([true, '1'])(
    'rejects non-number role and status bodies: %p',
    async (value) => {
      const role = plainToInstance(AdminUserRoleDto, { role: value });
      const status = plainToInstance(AdminUserStatusDto, { status: value });

      expect(await validate(role)).not.toEqual([]);
      expect(await validate(status)).not.toEqual([]);
    },
  );

  it('validates player assignment filters and pagination bounds', async () => {
    const valid = plainToInstance(AdminPlayerQueryDto, {
      assignment: 'assigned',
      teamId: '4',
      page: '2',
      limit: '50',
    });
    const invalid = plainToInstance(AdminPlayerQueryDto, {
      assignment: 'someone',
      page: 0,
      limit: 51,
    });

    expect(await validate(valid)).toEqual([]);
    expect(valid).toMatchObject({ teamId: 4, page: 2, limit: 50 });
    expect(await validate(invalid)).not.toEqual([]);
  });
});
