import { NotFoundException } from '@nestjs/common';
import { PublicTeamAccessService } from './public-team-access.service';

describe('PublicTeamAccessService', () => {
  const teamRepository = {
    createQueryBuilder: jest.fn(),
    findOne: jest.fn(),
    save: jest.fn(async (team) => team),
  };
  const playerRepository = { find: jest.fn(), findOne: jest.fn() };
  const transactionRepository = { find: jest.fn() };
  const penaltyRepository = { find: jest.fn() };
  const access = { assertMember: jest.fn(), assertManager: jest.fn() };
  let service: PublicTeamAccessService;

  const managedTeam = {
    id: 7,
    name: 'Team A',
    balance: 125,
    publicAccessEnabled: false,
    publicAccessToken: null,
  };

  beforeEach(() => {
    jest.resetAllMocks();
    teamRepository.save.mockImplementation(async (team) => team);
    service = new PublicTeamAccessService(
      teamRepository as any,
      playerRepository as any,
      transactionRepository as any,
      penaltyRepository as any,
      access as any,
    );
  });

  function mockManagedTeam(team = { ...managedTeam }) {
    const getOne = jest.fn().mockResolvedValue(team);
    teamRepository.createQueryBuilder.mockReturnValue({
      addSelect: jest.fn().mockReturnThis(),
      where: jest.fn().mockReturnThis(),
      getOne,
    });
    return team;
  }

  it('creates a 64 character token on first activation', async () => {
    const team = mockManagedTeam();

    const status = await service.setEnabled(4, 7, true);

    expect(access.assertManager).toHaveBeenCalledWith(4, 7);
    expect(status.enabled).toBe(true);
    expect(status.token).toMatch(/^[a-f0-9]{64}$/);
    expect(teamRepository.save).toHaveBeenCalledWith(team);
  });

  it('keeps the existing token when re-enabled', async () => {
    mockManagedTeam({ ...managedTeam, publicAccessToken: 'a'.repeat(64) });

    const status = await service.setEnabled(4, 7, true);

    expect(status.token).toBe('a'.repeat(64));
  });

  it('rotates the token without changing enabled state', async () => {
    mockManagedTeam({
      ...managedTeam,
      publicAccessEnabled: true,
      publicAccessToken: 'a'.repeat(64),
    });

    const status = await service.rotate(4, 7);

    expect(status.enabled).toBe(true);
    expect(status.token).toMatch(/^[a-f0-9]{64}$/);
    expect(status.token).not.toBe('a'.repeat(64));
  });

  it('returns only whitelisted public team fields and active players', async () => {
    teamRepository.findOne.mockResolvedValue({
      id: 7,
      name: 'Team A',
      balance: 125,
    });
    playerRepository.find.mockResolvedValue([
      {
        id: 3,
        firstName: 'Ada',
        lastName: 'Lovelace',
        balance: -20,
        active: true,
        user: { email: 'must-not-leak@example.com' },
      },
    ]);
    penaltyRepository.find.mockResolvedValue([
      { id: 8, description: 'Zu spät', amount: 5, user: { email: 'hidden' } },
    ]);

    await expect(service.getPublicTeam('b'.repeat(64))).resolves.toEqual({
      name: 'Team A',
      balance: 125,
      outstanding: 20,
      players: [
        {
          id: 3,
          firstName: 'Ada',
          lastName: 'Lovelace',
          balance: -20,
          active: true,
        },
      ],
      penalties: [{ id: 8, description: 'Zu spät', amount: 5 }],
    });
  });

  it('rejects a player that does not belong to the shared team', async () => {
    teamRepository.findOne.mockResolvedValue({
      id: 7,
      name: 'Team A',
      balance: 125,
    });
    playerRepository.findOne.mockResolvedValue(null);

    await expect(
      service.getPublicPlayerHistory('b'.repeat(64), 99),
    ).rejects.toBeInstanceOf(NotFoundException);
    expect(transactionRepository.find).not.toHaveBeenCalled();
  });

  it('returns player identity and the latest 20 transactions', async () => {
    teamRepository.findOne.mockResolvedValue({
      id: 7,
      name: 'Team A',
      balance: 125,
    });
    playerRepository.findOne.mockResolvedValue({
      id: 3,
      firstName: 'Ada',
      lastName: 'Lovelace',
      balance: -20,
      active: true,
    });
    transactionRepository.find.mockResolvedValue([
      {
        id: 11,
        note: 'Training',
        date: '2026-07-30',
        amount: 5,
        type: { id: 11, name: 'fine' },
        player: { id: 3 },
      },
    ]);

    await expect(
      service.getPublicPlayerHistory('b'.repeat(64), 3),
    ).resolves.toEqual({
      player: {
        id: 3,
        firstName: 'Ada',
        lastName: 'Lovelace',
        balance: -20,
        active: true,
      },
      transactions: [
        {
          id: 11,
          note: 'Training',
          date: '2026-07-30',
          amount: 5,
          type: { id: 11, name: 'fine' },
        },
      ],
    });
    expect(transactionRepository.find).toHaveBeenCalledWith(
      expect.objectContaining({ order: { date: 'DESC' }, take: 20 }),
    );
  });
});
