import { ForbiddenException } from '@nestjs/common';
import { RoleEnum } from '../roles/roles.enum';
import { TeamRolesEnum } from '../team-roles/team-roles.enum';
import { TeamAccessService } from './team-access.service';

describe('TeamAccessService', () => {
  const userRepository = { findOne: jest.fn() };
  const playerRepository = { find: jest.fn() };
  const teamSettingRepository = { findOne: jest.fn() };
  let service: TeamAccessService;

  beforeEach(() => {
    jest.resetAllMocks();
    service = new TeamAccessService(
      userRepository as any,
      playerRepository as any,
      teamSettingRepository as any,
    );
  });

  it('allows a global admin without a team membership', async () => {
    userRepository.findOne.mockResolvedValue({
      id: 1,
      role: { id: RoleEnum.admin },
    });

    await expect(service.assertManager(1, 9)).resolves.toBeUndefined();
    expect(playerRepository.find).not.toHaveBeenCalled();
  });

  it('allows an active team member to read sharing status', async () => {
    userRepository.findOne.mockResolvedValue({
      id: 2,
      role: { id: RoleEnum.user },
    });
    playerRepository.find.mockResolvedValue([
      { active: true, teamRole: { id: 1 } },
    ]);

    await expect(service.assertMember(2, 9)).resolves.toBeUndefined();
  });

  it.each([3, 4, 5])(
    'allows active team role %s to manage sharing',
    async (roleId) => {
      userRepository.findOne.mockResolvedValue({
        id: 2,
        role: { id: RoleEnum.user },
      });
      playerRepository.find.mockResolvedValue([
        { active: true, teamRole: { id: roleId } },
      ]);

      await expect(service.assertManager(2, 9)).resolves.toBeUndefined();
    },
  );

  it('uses the highest role when one user has multiple players in a team', async () => {
    userRepository.findOne.mockResolvedValue({
      id: 2,
      role: { id: RoleEnum.user },
    });
    playerRepository.find.mockResolvedValue([
      { active: true, teamRole: { id: 1 } },
      { active: true, teamRole: { id: 3 } },
    ]);

    await expect(service.assertManager(2, 9)).resolves.toBeUndefined();
  });

  it('uses repositories from the supplied transaction manager', async () => {
    const transactionalUserRepository = {
      findOne: jest.fn().mockResolvedValue({
        id: 2,
        role: { id: RoleEnum.user },
      }),
    };
    const transactionalPlayerRepository = {
      find: jest
        .fn()
        .mockResolvedValue([{ active: true, teamRole: { id: 3 } }]),
    };
    const manager = {
      getRepository: jest
        .fn()
        .mockReturnValueOnce(transactionalUserRepository)
        .mockReturnValueOnce(transactionalPlayerRepository),
    };

    await expect(service.assertManager(2, 9, manager as any)).resolves.toBeUndefined();
    expect(userRepository.findOne).not.toHaveBeenCalled();
    expect(playerRepository.find).not.toHaveBeenCalled();
  });

  it.each([
    ['a player', [{ active: true, teamRole: { id: 1 } }]],
    ['a second treasurer', [{ active: true, teamRole: { id: 2 } }]],
    ['an inactive captain', [{ active: false, teamRole: { id: 3 } }]],
    ['a non-member', []],
  ])('rejects %s from managing sharing', async (_label, players) => {
    userRepository.findOne.mockResolvedValue({
      id: 2,
      role: { id: RoleEnum.user },
    });
    playerRepository.find.mockResolvedValue(players);

    await expect(service.assertManager(2, 9)).rejects.toBeInstanceOf(
      ForbiddenException,
    );
  });

  describe('assertAtLeast', () => {
    it('uses the configured team setting instead of the fallback', async () => {
      userRepository.findOne.mockResolvedValue({
        id: 2,
        role: { id: RoleEnum.user },
      });
      teamSettingRepository.findOne.mockResolvedValue({ value: '4' });
      playerRepository.find.mockResolvedValue([
        { active: true, teamRole: { id: 3 } },
      ]);

      await expect(
        service.assertAtLeast(2, 9, 'invite_min_role', TeamRolesEnum.captain),
      ).rejects.toBeInstanceOf(ForbiddenException);
    });

    it('falls back to the given minimum role when no setting exists', async () => {
      userRepository.findOne.mockResolvedValue({
        id: 2,
        role: { id: RoleEnum.user },
      });
      teamSettingRepository.findOne.mockResolvedValue(null);
      playerRepository.find.mockResolvedValue([
        { active: true, teamRole: { id: 3 } },
      ]);

      await expect(
        service.assertAtLeast(2, 9, 'invite_min_role', TeamRolesEnum.captain),
      ).resolves.toBeUndefined();
    });

    it('falls back to the given minimum role when the stored value is invalid', async () => {
      userRepository.findOne.mockResolvedValue({
        id: 2,
        role: { id: RoleEnum.user },
      });
      teamSettingRepository.findOne.mockResolvedValue({ value: 'not-a-role' });
      playerRepository.find.mockResolvedValue([
        { active: true, teamRole: { id: 3 } },
      ]);

      await expect(
        service.assertAtLeast(2, 9, 'invite_min_role', TeamRolesEnum.captain),
      ).resolves.toBeUndefined();
    });

    it('lets a global admin bypass the check without reading settings', async () => {
      userRepository.findOne.mockResolvedValue({
        id: 1,
        role: { id: RoleEnum.admin },
      });

      await expect(
        service.assertAtLeast(1, 9, 'invite_min_role', TeamRolesEnum.captain),
      ).resolves.toBeUndefined();
      expect(teamSettingRepository.findOne).not.toHaveBeenCalled();
    });

    it('reads the team setting from the supplied transaction manager', async () => {
      const transactionalUserRepository = {
        findOne: jest.fn().mockResolvedValue({
          id: 2,
          role: { id: RoleEnum.user },
        }),
      };
      const transactionalPlayerRepository = {
        find: jest
          .fn()
          .mockResolvedValue([{ active: true, teamRole: { id: 4 } }]),
      };
      const transactionalTeamSettingRepository = {
        findOne: jest.fn().mockResolvedValue({ value: '4' }),
      };
      const manager = {
        getRepository: jest
          .fn()
          .mockImplementation((entity) => {
            if (entity.name === 'TeamSetting') {
              return transactionalTeamSettingRepository;
            }
            if (entity.name === 'User') return transactionalUserRepository;
            return transactionalPlayerRepository;
          }),
      };

      await expect(
        service.assertAtLeast(
          2,
          9,
          'penalty_manage_min_role',
          TeamRolesEnum.captain,
          manager as any,
        ),
      ).resolves.toBeUndefined();
      expect(teamSettingRepository.findOne).not.toHaveBeenCalled();
      expect(userRepository.findOne).not.toHaveBeenCalled();
      expect(playerRepository.find).not.toHaveBeenCalled();
    });
  });
});
