import { ForbiddenException, UnauthorizedException } from '@nestjs/common';
import { PermissionService } from './permission.service';
import type { CurrentUser } from './auth-context.service';

describe('PermissionService', () => {
  const versionFindUnique = jest.fn();
  const projectMemberFindUnique = jest.fn();

  const prisma = {
    version: { findUnique: versionFindUnique },
    projectMember: { findUnique: projectMemberFindUnique },
  } as any;

  const service = new PermissionService(prisma);

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

  it('allows the built-in super admin wildcard for any permission without a scope lookup', async () => {
    const user = currentUser({ id: 'm-8', roleId: 'role-admin' });

    await expect(service.can(user, 'audit:view', { versionId: 'version-1' })).resolves.toBe(true);

    expect(versionFindUnique).not.toHaveBeenCalled();
    expect(projectMemberFindUnique).not.toHaveBeenCalled();
  });

  it('requires scoped users to belong to the project resolved from a version', async () => {
    versionFindUnique.mockResolvedValue({ id: 'version-1', projectId: 'project-1', members: [] });
    projectMemberFindUnique.mockResolvedValue({ projectId: 'project-1', userId: 'dev-1', role: 'member' });

    await expect(service.can(currentUser({ id: 'dev-1', roleId: 'role-dev' }), 'version.devtask:manage', {
      versionId: 'version-1',
    })).resolves.toBe(true);

    expect(versionFindUnique).toHaveBeenCalledWith({
      where: { id: 'version-1' },
      select: { id: true, projectId: true, members: true },
    });
    expect(projectMemberFindUnique).toHaveBeenCalledWith({
      where: { projectId_userId: { projectId: 'project-1', userId: 'dev-1' } },
    });
  });

  it('allows a version member when no project membership exists', async () => {
    versionFindUnique.mockResolvedValue({
      id: 'version-1',
      projectId: 'project-1',
      members: [{ id: 'tester-1', name: 'Tester One' }],
    });
    projectMemberFindUnique.mockResolvedValue(null);

    await expect(service.can(currentUser({ id: 'tester-1', name: 'Tester One', roleId: 'role-test' }), 'version.testcase:manage', {
      versionId: 'version-1',
    })).resolves.toBe(true);
  });

  it('denies users who have a global role permission but are outside the resource scope', async () => {
    versionFindUnique.mockResolvedValue({ id: 'version-1', projectId: 'project-1', members: [] });
    projectMemberFindUnique.mockResolvedValue(null);

    await expect(service.can(currentUser({ id: 'dev-1', roleId: 'role-dev' }), 'version.devtask:manage', {
      versionId: 'version-1',
    })).resolves.toBe(false);
  });

  it('throws explicit auth exceptions for assertion callers', async () => {
    await expect(service.assertCan(null, 'product:create')).rejects.toBeInstanceOf(UnauthorizedException);
    await expect(service.assertCan(currentUser({ roleId: 'role-dev' }), 'product:delete')).rejects.toBeInstanceOf(ForbiddenException);
  });
});

function currentUser(overrides: Partial<CurrentUser>): CurrentUser {
  return {
    id: 'user-1',
    name: 'User One',
    username: 'user.one',
    roleId: 'role-dev',
    email: '',
    ...overrides,
  };
}
