import { PermissionGuard } from './permission.guard';
import { CaslAbilityFactory } from '../../authorization/casl-ability.factory';
import { CaslAction, permissionCodeSubject } from '../../authorization/casl.constants';

describe('PermissionGuard', () => {
  const abilityFactory = new CaslAbilityFactory();

  /** Mock NestJS ExecutionContext with reflector overrides */
  function createContext(
    user: unknown,
    overrides: {
      isPublic?: boolean;
      authenticatedOnly?: boolean;
      permissions?: string[];
      checkPolicies?: unknown[];
    } = {},
  ) {
    const meta = new Map<string, unknown>();
    if (overrides.isPublic !== undefined) meta.set('isPublic', overrides.isPublic);
    if (overrides.authenticatedOnly !== undefined)
      meta.set('authenticatedOnly', overrides.authenticatedOnly);
    if (overrides.permissions) meta.set('permissions', overrides.permissions);
    if (overrides.checkPolicies) meta.set('check_policies', overrides.checkPolicies);

    const reflector = {
      getAllAndOverride: jest.fn((key: string) => meta.get(key) ?? undefined),
      getAllAndMerge: jest.fn((key: string) => meta.get(key) ?? []),
    };
    const guard = new PermissionGuard(reflector as never, abilityFactory);
    return guard.canActivate({
      getHandler: () => function handler() {},
      getClass: () => class Controller {},
      switchToHttp: () => ({ getRequest: () => ({ user }) }),
    } as never);
  }

  // -----------------------------------------------------------------------
  //  @Public / @Authenticated / undeclared
  // -----------------------------------------------------------------------

  it('denies routes that forgot to declare permissions', () => {
    expect(createContext(undefined)).toBe(false);
    expect(createContext({ permissions: [], isSuperAdmin: false })).toBe(false);
  });

  it('allows explicitly public routes without a user', () => {
    expect(createContext(undefined, { isPublic: true })).toBe(true);
  });

  it('allows authenticated-only routes for logged-in users', () => {
    expect(
      createContext({ permissions: [], isSuperAdmin: false }, { authenticatedOnly: true }),
    ).toBe(true);
  });

  it('denies authenticated-only routes when no user is present', () => {
    expect(createContext(undefined, { authenticatedOnly: true })).toBe(false);
  });

  it('does not let controller-level @Authenticated bypass handler permissions', () => {
    expect(
      createContext(
        { permissions: [], isSuperAdmin: false },
        { authenticatedOnly: true, permissions: ['user:edit'] },
      ),
    ).toBe(false);
  });

  it('still enforces policies when @Authenticated and @CheckPolicies coexist', () => {
    expect(
      createContext(
        { permissions: [], isSuperAdmin: false },
        {
          authenticatedOnly: true,
          checkPolicies: [(ability: any) => ability.can('read', 'Student')],
        },
      ),
    ).toBe(true);
  });

  // -----------------------------------------------------------------------
  //  @CheckPolicies passthrough
  // -----------------------------------------------------------------------

  it('allows pass-through for @CheckPolicies routes with user present', () => {
    expect(
      createContext(
        { permissions: [], isSuperAdmin: false },
        { checkPolicies: [(ab: any) => ab.can('read', 'Student')] },
      ),
    ).toBe(true);
  });

  it('denies pass-through for @CheckPolicies routes without user', () => {
    expect(
      createContext(undefined, {
        checkPolicies: [(ab: any) => ab.can('read', 'Student')],
      }),
    ).toBe(false);
  });

  // -----------------------------------------------------------------------
  //  CASL exact-code authorization (collision-free)
  // -----------------------------------------------------------------------

  it('grants access to super admin for any permission', () => {
    expect(
      createContext({ permissions: [], isSuperAdmin: true }, { permissions: ['student:view'] }),
    ).toBe(true);
  });

  it('grants access when user has the exact required permission', () => {
    expect(
      createContext(
        { permissions: ['student:view'], isSuperAdmin: false },
        { permissions: ['student:view'] },
      ),
    ).toBe(true);
  });

  it('denies access when user lacks the required permission', () => {
    expect(
      createContext(
        { permissions: ['student:view'], isSuperAdmin: false },
        { permissions: ['class:delete'] },
      ),
    ).toBe(false);
  });

  it('denies access when user has no permissions', () => {
    expect(
      createContext({ permissions: [], isSuperAdmin: false }, { permissions: ['student:view'] }),
    ).toBe(false);
  });

  it('denies unknown permission codes (no user holds them)', () => {
    expect(
      createContext(
        { permissions: ['unknown:action'], isSuperAdmin: false },
        { permissions: ['other:thing'] },
      ),
    ).toBe(false);
  });

  it('grants exact-code access for custom permissions', () => {
    expect(
      createContext(
        { permissions: ['custom:special'], isSuperAdmin: false },
        { permissions: ['custom:special'] },
      ),
    ).toBe(true);
  });

  it('grants with OR matching: one of multiple required permissions', () => {
    expect(
      createContext(
        { permissions: ['class:view'], isSuperAdmin: false },
        { permissions: ['student:delete', 'class:view'] },
      ),
    ).toBe(true);
  });

  // ── Collision regression tests ──

  it('denies bill:export-excel when user only has bill:view', () => {
    const ability = abilityFactory.createForUser({
      permissions: ['bill:view'],
      isSuperAdmin: false,
    });

    // Domain layer: both would give read Bill — but exact-code check must discriminate
    expect(ability.can(CaslAction.Read, 'Bill')).toBe(true);
    // Exact-code check: bill:view user must NOT have bill:export-excel
    expect(ability.can(CaslAction.Access, permissionCodeSubject('bill:export-excel'))).toBe(false);
  });

  it('denies bill:confirm when user only has bill:view', () => {
    const ability = abilityFactory.createForUser({
      permissions: ['bill:view'],
      isSuperAdmin: false,
    });

    // Domain layer: confirm → update, view → read — already distinct at domain level
    expect(ability.can(CaslAction.Update, 'Bill')).toBe(false);
    // Exact-code: must also fail
    expect(ability.can(CaslAction.Access, permissionCodeSubject('bill:confirm'))).toBe(false);
  });

  it('denies deposit:approve when user only has deposit:edit', () => {
    const ability = abilityFactory.createForUser({
      permissions: ['deposit:edit'],
      isSuperAdmin: false,
    });

    // Domain layer: both map to update — would pass domain check
    expect(ability.can(CaslAction.Update, 'Deposit')).toBe(true);
    // Exact-code: must fail — edit is not approve
    expect(ability.can(CaslAction.Access, permissionCodeSubject('deposit:approve'))).toBe(false);
  });

  it('denies attendance:export when user only has attendance:view', () => {
    const ability = abilityFactory.createForUser({
      permissions: ['attendance:view'],
      isSuperAdmin: false,
    });

    // Domain layer: both map to read
    expect(ability.can(CaslAction.Read, 'Attendance')).toBe(true);
    // Exact-code: must fail
    expect(ability.can(CaslAction.Access, permissionCodeSubject('attendance:export'))).toBe(false);
  });

  it('unknown code student:nuke does not create domain ability', () => {
    const ability = abilityFactory.createForUser({
      permissions: ['student:nuke'],
      isSuperAdmin: false,
    });

    expect(ability.can(CaslAction.Manage, 'Student')).toBe(false);
    expect(ability.can(CaslAction.Read, 'Student')).toBe(false);
    expect(ability.can(CaslAction.Access, permissionCodeSubject('student:nuke'))).toBe(true);
  });
});
