import { describe, expect, it, vi } from 'vitest';
import {
  EntraGroupMapError,
  EntraGroupToRoleResolver,
  parseEntraGroupMap,
} from './entra-group-to-role';

const GUID_A = '11111111-1111-1111-1111-111111111111';
const GUID_B = '22222222-2222-2222-2222-222222222222';
const GUID_C = '33333333-3333-3333-3333-333333333333';

describe('parseEntraGroupMap', () => {
  it('builds a frozen lower-cased map from a valid record', () => {
    const map = parseEntraGroupMap({
      [GUID_A]: 'collaborateur',
      [GUID_B]: 'rh',
    });
    expect(map.get(GUID_A)).toBe('collaborateur');
    expect(map.get(GUID_B)).toBe('rh');
    expect(map.size).toBe(2);
  });

  it('lower-cases GUIDs so claim-side case variance does not miss', () => {
    const upper = '11111111-1111-1111-1111-1111111111AB';
    const map = parseEntraGroupMap({ [upper]: 'collaborateur' });
    expect(map.get(upper.toLowerCase())).toBe('collaborateur');
  });

  it('rejects a value that is not in the closed catalogue', () => {
    expect(() => parseEntraGroupMap({ [GUID_A]: 'rogue-role' })).toThrow(EntraGroupMapError);
  });

  it('rejects a key that does not look like a GUID', () => {
    expect(() => parseEntraGroupMap({ 'not-a-guid': 'collaborateur' })).toThrow(EntraGroupMapError);
  });

  it('rejects a duplicate GUID (case-insensitive)', () => {
    // Use a GUID containing hex letters so upper/lower casing
    // produces two distinct strings (object literals dedup
    // identical keys before iteration).
    const lower = 'aaaabbbb-cccc-dddd-eeee-ffff00001111';
    const upper = lower.toUpperCase();
    expect(() =>
      parseEntraGroupMap({
        [lower]: 'collaborateur',
        [upper]: 'rh',
      }),
    ).toThrow(EntraGroupMapError);
  });
});

describe('EntraGroupToRoleResolver', () => {
  const map = parseEntraGroupMap({
    [GUID_A]: 'collaborateur',
    [GUID_B]: 'directeur-etablissement',
  });
  const resolver = new EntraGroupToRoleResolver(map);

  it('resolves known group GUIDs to catalogue role slugs', () => {
    expect(resolver.resolve([GUID_A, GUID_B])).toEqual([
      'collaborateur',
      'directeur-etablissement',
    ]);
  });

  it('orders the output by FUNCTIONAL_ROLES declaration, not claim order', () => {
    // Claim order is `directeur-etablissement` first, then
    // `collaborateur`. Resolver re-orders for deterministic logs
    // and snapshot tests.
    expect(resolver.resolve([GUID_B, GUID_A])).toEqual([
      'collaborateur',
      'directeur-etablissement',
    ]);
  });

  it('deduplicates if the same GUID appears twice in the claim', () => {
    expect(resolver.resolve([GUID_A, GUID_A])).toEqual(['collaborateur']);
  });

  it('skips unknown GUIDs and reports them via the optional callback', () => {
    const onUnknown = vi.fn();
    const out = resolver.resolve([GUID_A, GUID_C], onUnknown);
    expect(out).toEqual(['collaborateur']);
    expect(onUnknown).toHaveBeenCalledTimes(1);
    expect(onUnknown).toHaveBeenCalledWith(GUID_C);
  });

  it('returns an empty list when the user has no groups', () => {
    expect(resolver.resolve([])).toEqual([]);
  });

  it('coveredRoles() returns only mapped roles, in catalogue order', () => {
    expect(resolver.coveredRoles()).toEqual(['collaborateur', 'directeur-etablissement']);
  });
});
