import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import fc from 'fast-check';
import { Arbiter } from '../../src/core/Arbiter.js';

describe('Fast-check: tuple-to-userset invariants', () => {
  test('tuple_to_userset matches model', () => {
    fc.assert(
      fc.property(
        fc.integer({ min: 1, max: 5 }),
        fc.integer({ min: 1, max: 5 }),
        fc.integer({ min: 1, max: 5 }),
        fc.array(
          fc.record({
            doc: fc.integer({ min: 0, max: 4 }),
            group: fc.integer({ min: 0, max: 4 })
          }),
          { minLength: 0, maxLength: 20 }
        ),
        fc.array(
          fc.record({
            user: fc.integer({ min: 0, max: 4 }),
            group: fc.integer({ min: 0, max: 4 })
          }),
          { minLength: 0, maxLength: 20 }
        ),
        (userCount, groupCount, docCount, owners, members) => {
          const arbiter = new Arbiter();
          for (let u = 0; u < userCount; u++) arbiter.addNode(`user:${u}`, 'user');
          for (let g = 0; g < groupCount; g++) arbiter.addNode(`group:${g}`, 'group');
          for (let d = 0; d < docCount; d++) arbiter.addNode(`doc:${d}`, 'doc');

          arbiter.setRelationConfig('owner', { type: 'direct' });
          arbiter.setRelationConfig('member', { type: 'direct' });
          arbiter.setRelationConfig('access', {
            type: 'tuple_to_userset',
            tuplesetRelation: 'owner',
            computedRelation: 'member'
          });

          const ownersByDoc = new Map();
          const membersByGroup = new Map();

          for (const edge of owners) {
            const doc = edge.doc % docCount;
            const group = edge.group % groupCount;
            arbiter.addRelation(`doc:${doc}`, 'owner', `group:${group}`, 1.0);
            const set = ownersByDoc.get(doc) || new Set();
            set.add(group);
            ownersByDoc.set(doc, set);
          }

          for (const edge of members) {
            const user = edge.user % userCount;
            const group = edge.group % groupCount;
            arbiter.addRelation(`user:${user}`, 'member', `group:${group}`, 1.0);
            const set = membersByGroup.get(group) || new Set();
            set.add(user);
            membersByGroup.set(group, set);
          }

          for (let u = 0; u < userCount; u++) {
            for (let d = 0; d < docCount; d++) {
              const result = arbiter.check(`user:${u}`, 'access', `doc:${d}`);
              const groups = ownersByDoc.get(d) || new Set();
              let expected = false;
              for (const group of groups) {
                const membersSet = membersByGroup.get(group) || new Set();
                if (membersSet.has(u)) {
                  expected = true;
                  break;
                }
              }
              assert.strictEqual(result.possibility > 0, expected);
            }
          }
        }
      ),
      { numRuns: 30 }
    );
  });
});
