import { describe, it, expect } from 'vitest';
import { ResourceRuleRegistry } from '../src/validation/resource-rules.js';
import type { ResourceRule, RuleMeta, RuleContext, ValidationResult } from '../src/validation/resource-rules.js';

const noopCtx: RuleContext = {
  findResource: async () => null,
};

function makeRule(
  name: string,
  resource: string,
  matchFn: (data: unknown, meta: RuleMeta) => boolean,
  validateFn: () => Promise<ValidationResult>,
): ResourceRule {
  return { name, resource, match: matchFn, validate: validateFn };
}

describe('ResourceRuleRegistry', () => {
  it('returns valid when no rules are registered', async () => {
    const registry = new ResourceRuleRegistry();
    const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx);
    expect(result.valid).toBe(true);
  });

  it('returns valid when no rules match the resource type', async () => {
    const registry = new ResourceRuleRegistry();
    registry.register(makeRule('server-rule', 'server', () => true, async () => ({ valid: false, errors: ['fail'] })));

    const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx);
    expect(result.valid).toBe(true);
  });

  it('skips rules that do not match the predicate', async () => {
    const registry = new ResourceRuleRegistry();
    registry.register(makeRule('skip-rule', 'prompt', () => false, async () => ({ valid: false, errors: ['fail'] })));

    const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx);
    expect(result.valid).toBe(true);
  });

  it('runs matching rules and returns errors', async () => {
    const registry = new ResourceRuleRegistry();
    registry.register(makeRule('bad-rule', 'prompt', () => true, async () => ({
      valid: false,
      errors: ['missing template var'],
    })));

    const result = await registry.validate('prompt', { name: 'x' }, { operation: 'update' }, noopCtx);
    expect(result.valid).toBe(false);
    expect(result.errors).toEqual(['missing template var']);
  });

  it('aggregates errors from multiple matching rules', async () => {
    const registry = new ResourceRuleRegistry();
    registry.register(makeRule('rule-1', 'prompt', () => true, async () => ({
      valid: false,
      errors: ['error A'],
    })));
    registry.register(makeRule('rule-2', 'prompt', () => true, async () => ({
      valid: false,
      errors: ['error B', 'error C'],
    })));

    const result = await registry.validate('prompt', { name: 'x' }, { operation: 'create' }, noopCtx);
    expect(result.valid).toBe(false);
    expect(result.errors).toEqual(['error A', 'error B', 'error C']);
  });

  it('returns valid when all matching rules pass', async () => {
    const registry = new ResourceRuleRegistry();
    registry.register(makeRule('ok-rule', 'prompt', () => true, async () => ({ valid: true })));

    const result = await registry.validate('prompt', { name: 'x' }, { operation: 'update' }, noopCtx);
    expect(result.valid).toBe(true);
  });

  it('passes meta to match function', async () => {
    const registry = new ResourceRuleRegistry();
    let receivedMeta: RuleMeta | null = null;
    registry.register(makeRule('meta-check', 'prompt', (_data, meta) => {
      receivedMeta = meta;
      return false;
    }, async () => ({ valid: true })));

    await registry.validate('prompt', { name: 'x' }, { operation: 'update', isSystemResource: true, projectName: 'sys' }, noopCtx);
    expect(receivedMeta).toEqual({ operation: 'update', isSystemResource: true, projectName: 'sys' });
  });
});
