/**
 * Unit tests for FederationScopeSchema and parseFederationScope.
 *
 * Coverage:
 *  - Valid: minimal scope
 *  - Valid: full PRD §8.1 example
 *  - Valid: resources + excluded_resources (no overlap)
 *  - Invalid: empty resources
 *  - Invalid: unknown resource value
 *  - Invalid: resources / excluded_resources intersection
 *  - Invalid: filter key not in resources
 *  - Invalid: max_rows_per_query = 0
 *  - Invalid: max_rows_per_query = 10001
 *  - Invalid: not an object / null
 *  - Defaults: include_personal defaults to true; excluded_resources defaults to []
 *  - Sentinel: console.warn fires for sensitive resources
 */

import { describe, it, expect, vi, afterEach } from 'vitest';
import {
  parseFederationScope,
  FederationScopeError,
  FederationScopeSchema,
} from './scope-schema.js';

afterEach(() => {
  vi.restoreAllMocks();
});

describe('parseFederationScope — valid inputs', () => {
  it('accepts a minimal scope (resources + max_rows_per_query only)', () => {
    const scope = parseFederationScope({
      resources: ['tasks'],
      max_rows_per_query: 100,
    });
    expect(scope.resources).toEqual(['tasks']);
    expect(scope.max_rows_per_query).toBe(100);
    expect(scope.excluded_resources).toEqual([]);
    expect(scope.filters).toBeUndefined();
  });

  it('accepts the full PRD §8.1 example', () => {
    const scope = parseFederationScope({
      resources: ['tasks', 'notes', 'memory'],
      filters: {
        tasks: { include_teams: ['team_uuid_1', 'team_uuid_2'], include_personal: true },
        notes: { include_personal: true, include_teams: [] },
        memory: { include_personal: true },
      },
      excluded_resources: ['credentials', 'api_keys'],
      max_rows_per_query: 500,
    });
    expect(scope.resources).toEqual(['tasks', 'notes', 'memory']);
    expect(scope.excluded_resources).toEqual(['credentials', 'api_keys']);
    expect(scope.filters?.tasks?.include_teams).toEqual(['team_uuid_1', 'team_uuid_2']);
    expect(scope.max_rows_per_query).toBe(500);
  });

  it('accepts a scope with excluded_resources and no filter overlap', () => {
    const scope = parseFederationScope({
      resources: ['tasks', 'notes'],
      excluded_resources: ['memory'],
      max_rows_per_query: 250,
    });
    expect(scope.resources).toEqual(['tasks', 'notes']);
    expect(scope.excluded_resources).toEqual(['memory']);
  });
});

describe('parseFederationScope — defaults', () => {
  it('defaults excluded_resources to []', () => {
    const scope = parseFederationScope({ resources: ['tasks'], max_rows_per_query: 1 });
    expect(scope.excluded_resources).toEqual([]);
  });

  it('defaults include_personal to true when filter is provided without it', () => {
    const scope = parseFederationScope({
      resources: ['tasks'],
      filters: { tasks: { include_teams: ['t1'] } },
      max_rows_per_query: 10,
    });
    expect(scope.filters?.tasks?.include_personal).toBe(true);
  });
});

describe('parseFederationScope — invalid inputs', () => {
  it('throws FederationScopeError for empty resources array', () => {
    expect(() => parseFederationScope({ resources: [], max_rows_per_query: 100 })).toThrow(
      FederationScopeError,
    );
  });

  it('throws for unknown resource value in resources', () => {
    expect(() =>
      parseFederationScope({ resources: ['unknown_resource'], max_rows_per_query: 100 }),
    ).toThrow(FederationScopeError);
  });

  it('throws when resources and excluded_resources intersect', () => {
    expect(() =>
      parseFederationScope({
        resources: ['tasks', 'memory'],
        excluded_resources: ['memory'],
        max_rows_per_query: 100,
      }),
    ).toThrow(FederationScopeError);
  });

  it('throws when filters references a resource not in resources', () => {
    expect(() =>
      parseFederationScope({
        resources: ['tasks'],
        filters: { notes: { include_personal: true } },
        max_rows_per_query: 100,
      }),
    ).toThrow(FederationScopeError);
  });

  it('throws for max_rows_per_query = 0', () => {
    expect(() => parseFederationScope({ resources: ['tasks'], max_rows_per_query: 0 })).toThrow(
      FederationScopeError,
    );
  });

  it('throws for max_rows_per_query = 10001', () => {
    expect(() => parseFederationScope({ resources: ['tasks'], max_rows_per_query: 10001 })).toThrow(
      FederationScopeError,
    );
  });

  it('throws for null input', () => {
    expect(() => parseFederationScope(null)).toThrow(FederationScopeError);
  });

  it('throws for non-object input (string)', () => {
    expect(() => parseFederationScope('not-an-object')).toThrow(FederationScopeError);
  });
});

describe('parseFederationScope — sentinel warning', () => {
  it('emits console.warn when resources includes "credentials"', () => {
    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
    parseFederationScope({
      resources: ['tasks', 'credentials'],
      max_rows_per_query: 100,
    });
    expect(warnSpy).toHaveBeenCalledWith(
      expect.stringContaining(
        '[FederationScope] WARNING: scope grants sensitive resource "credentials"',
      ),
    );
  });

  it('emits console.warn when resources includes "api_keys"', () => {
    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
    parseFederationScope({
      resources: ['tasks', 'api_keys'],
      max_rows_per_query: 100,
    });
    expect(warnSpy).toHaveBeenCalledWith(
      expect.stringContaining(
        '[FederationScope] WARNING: scope grants sensitive resource "api_keys"',
      ),
    );
  });

  it('does NOT emit console.warn for non-sensitive resources', () => {
    const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
    parseFederationScope({ resources: ['tasks', 'notes', 'memory'], max_rows_per_query: 100 });
    expect(warnSpy).not.toHaveBeenCalled();
  });
});

describe('FederationScopeSchema — boundary values', () => {
  it('accepts max_rows_per_query = 1 (lower bound)', () => {
    const result = FederationScopeSchema.safeParse({ resources: ['tasks'], max_rows_per_query: 1 });
    expect(result.success).toBe(true);
  });

  it('accepts max_rows_per_query = 10000 (upper bound)', () => {
    const result = FederationScopeSchema.safeParse({
      resources: ['tasks'],
      max_rows_per_query: 10000,
    });
    expect(result.success).toBe(true);
  });
});
