import { ExecutionContext, ForbiddenException } from '@nestjs/common';
import { TripMembershipGuard } from './trip-membership.guard';

describe('TripMembershipGuard', () => {
  function context(
    params: Record<string, string>,
    user: { id: string },
  ): ExecutionContext {
    const req = { params, user };
    return {
      switchToHttp: () => ({ getRequest: () => req }),
      getHandler: () => ({}),
      getClass: () => ({}),
    } as unknown as ExecutionContext;
  }

  it('denies a user who has no trip_members row for the trip', async () => {
    const members = {
      findByTripAndUser: jest.fn().mockResolvedValue(undefined),
    };
    const reflector = {
      getAllAndOverride: jest.fn().mockReturnValue(undefined),
    };
    const guard = new TripMembershipGuard(members as never, reflector as never);

    await expect(
      guard.canActivate(context({ tripId: 't1' }, { id: 'u1' })),
    ).rejects.toThrow(ForbiddenException);
  });

  it('denies an ACTIVE MEMBER when the route requires OWNER', async () => {
    const members = {
      findByTripAndUser: jest
        .fn()
        .mockResolvedValue({ role: 'MEMBER', status: 'ACTIVE' }),
    };
    const reflector = {
      getAllAndOverride: jest.fn().mockReturnValue(['OWNER']),
    };
    const guard = new TripMembershipGuard(members as never, reflector as never);

    await expect(
      guard.canActivate(context({ tripId: 't1' }, { id: 'u1' })),
    ).rejects.toThrow(ForbiddenException);
  });

  it('denies an INVITED (not yet ACTIVE) member', async () => {
    const members = {
      findByTripAndUser: jest
        .fn()
        .mockResolvedValue({ role: 'MEMBER', status: 'INVITED' }),
    };
    const reflector = {
      getAllAndOverride: jest.fn().mockReturnValue(undefined),
    };
    const guard = new TripMembershipGuard(members as never, reflector as never);

    await expect(
      guard.canActivate(context({ tripId: 't1' }, { id: 'u1' })),
    ).rejects.toThrow(ForbiddenException);
  });

  it('allows an ACTIVE OWNER through an OWNER-only route', async () => {
    const members = {
      findByTripAndUser: jest
        .fn()
        .mockResolvedValue({ role: 'OWNER', status: 'ACTIVE' }),
    };
    const reflector = {
      getAllAndOverride: jest.fn().mockReturnValue(['OWNER']),
    };
    const guard = new TripMembershipGuard(members as never, reflector as never);

    await expect(
      guard.canActivate(context({ tripId: 't1' }, { id: 'u1' })),
    ).resolves.toBe(true);
  });

  it('allows an ACTIVE MEMBER through a route with no role restriction', async () => {
    const members = {
      findByTripAndUser: jest
        .fn()
        .mockResolvedValue({ role: 'MEMBER', status: 'ACTIVE' }),
    };
    const reflector = {
      getAllAndOverride: jest.fn().mockReturnValue(undefined),
    };
    const guard = new TripMembershipGuard(members as never, reflector as never);

    await expect(
      guard.canActivate(context({ tripId: 't1' }, { id: 'u1' })),
    ).resolves.toBe(true);
  });
});
