/**
 * v7 Stage 1 — pure-function tests for the visibility predicate.
 * Lives separately from the service-level tests because the predicate
 * is the single source of truth for "can user X see this row" and is
 * called from both LlmService.list and AgentService.list. We exercise
 * every branch of the decision tree to lock the semantics in.
 */
import { describe, it, expect } from 'vitest';
import { isLlmVisibleTo, type Viewer } from '../src/services/llm.service.js';
import { isAgentVisibleTo, type AgentViewer } from '../src/services/agent.service.js';

const llmRow = (overrides: { name?: string; ownerId?: string | null; visibility?: string } = {}): { name: string; ownerId: string | null; visibility: string } => ({
  name: 'vllm-alice',
  ownerId: 'alice',
  visibility: 'private',
  ...overrides,
});

const agentRow = (overrides: { name?: string; ownerId?: string; visibility?: string } = {}): { name: string; ownerId: string; visibility: string } => ({
  name: 'reviewer',
  ownerId: 'alice',
  visibility: 'private',
  ...overrides,
});

describe('isLlmVisibleTo (v7)', () => {
  it('null viewer skips the filter — internal callers see everything', () => {
    // Cron sweeps, audit collectors, and tests without a request
    // context get a null viewer. The visibility filter is then a
    // no-op, which matches the pre-v7 behavior of those code paths.
    expect(isLlmVisibleTo(llmRow(), null)).toBe(true);
  });

  it('public rows are visible to anyone with the resource grant', () => {
    const v: Viewer = { userId: 'bob', wildcard: false, allowedNames: new Set() };
    expect(isLlmVisibleTo(llmRow({ visibility: 'public' }), v)).toBe(true);
  });

  it('wildcard viewer (admin) sees private rows owned by others', () => {
    const v: Viewer = { userId: 'admin', wildcard: true, allowedNames: new Set() };
    expect(isLlmVisibleTo(llmRow({ visibility: 'private', ownerId: 'alice' }), v)).toBe(true);
  });

  it('owner sees their own private row', () => {
    const v: Viewer = { userId: 'alice', wildcard: false, allowedNames: new Set() };
    expect(isLlmVisibleTo(llmRow({ visibility: 'private', ownerId: 'alice' }), v)).toBe(true);
  });

  it('non-owner without name-scoped grant cannot see a private row', () => {
    const v: Viewer = { userId: 'bob', wildcard: false, allowedNames: new Set() };
    expect(isLlmVisibleTo(llmRow({ visibility: 'private', ownerId: 'alice' }), v)).toBe(false);
  });

  it('non-owner WITH name-scoped grant can see a private row', () => {
    // alice published vllm-alice as private; alice ran
    // `mcpctl create rbac binding view:llms:vllm-alice --user bob`,
    // so bob now sees the row in his list output.
    const v: Viewer = { userId: 'bob', wildcard: false, allowedNames: new Set(['vllm-alice']) };
    expect(isLlmVisibleTo(llmRow({ name: 'vllm-alice', visibility: 'private', ownerId: 'alice' }), v)).toBe(true);
  });

  it('treats null ownerId as no-owner (legacy rows pre-v7 backfill stay visible if public)', () => {
    // The migration sets visibility='public' for legacy rows, so they
    // pass the public-visibility check before the ownerId branch is
    // ever reached. A row with NULL ownerId AND visibility='private'
    // is unreachable via normal flows, but we still want the predicate
    // to behave: no owner + bob viewing = not visible.
    const v: Viewer = { userId: 'bob', wildcard: false, allowedNames: new Set() };
    expect(isLlmVisibleTo(llmRow({ ownerId: null, visibility: 'private' }), v)).toBe(false);
  });
});

describe('isAgentVisibleTo (v7)', () => {
  // Same shape as Llm; agents always have a non-null ownerId because
  // `Agent.ownerId` is required, so we don't need the legacy-null
  // branch test.
  it('null viewer = visible (internal calls bypass filter)', () => {
    expect(isAgentVisibleTo(agentRow(), null)).toBe(true);
  });

  it('public agents visible to anyone with resource grant', () => {
    const v: AgentViewer = { userId: 'bob', wildcard: false, allowedNames: new Set() };
    expect(isAgentVisibleTo(agentRow({ visibility: 'public' }), v)).toBe(true);
  });

  it('owner sees own private agent', () => {
    const v: AgentViewer = { userId: 'alice', wildcard: false, allowedNames: new Set() };
    expect(isAgentVisibleTo(agentRow({ visibility: 'private', ownerId: 'alice' }), v)).toBe(true);
  });

  it('non-owner without grant blocked from private agent', () => {
    const v: AgentViewer = { userId: 'bob', wildcard: false, allowedNames: new Set() };
    expect(isAgentVisibleTo(agentRow({ visibility: 'private', ownerId: 'alice' }), v)).toBe(false);
  });

  it('non-owner WITH name-scoped grant can see private agent', () => {
    const v: AgentViewer = { userId: 'bob', wildcard: false, allowedNames: new Set(['reviewer']) };
    expect(isAgentVisibleTo(agentRow({ name: 'reviewer', visibility: 'private', ownerId: 'alice' }), v)).toBe(true);
  });

  it('wildcard viewer sees private agent owned by another user', () => {
    const v: AgentViewer = { userId: 'admin', wildcard: true, allowedNames: new Set() };
    expect(isAgentVisibleTo(agentRow({ visibility: 'private', ownerId: 'alice' }), v)).toBe(true);
  });
});
