/**
 * uat-plan/discover — pure correlation tests (no DB, no filesystem): path scoping,
 * component-key building, registry parsing, permission resolution, route/endpoint
 * correlation.
 */
import { describe, it, expect } from 'vitest';
import {
  pathToPrefix,
  inScope,
  buildComponentKeys,
  parseRegistry,
  resolvePagePermission,
  resolveEndpointPermission,
  correlateRoutes,
  correlateEndpoints,
} from '../discover.js';
import type { NavNode } from '../../../../lib/sql-discovery.js';
import type { ParsedEndpoint } from '../discover-endpoints.js';

function nav(over: Partial<NavNode> & Pick<NavNode, 'id' | 'code'>): NavNode {
  return {
    kind: 'section',
    label: '',
    parentId: '',
    route: '',
    isPersonal: false,
    ...over,
  };
}

const NODES: NavNode[] = [
  nav({ kind: 'application', id: 'a1', code: 'administration', label: 'Administration', route: '/administration' }),
  nav({ id: 's1', code: 'users', label: 'Users', parentId: 'a1', route: '/administration/users' }),
  nav({ id: 's2', code: 'roles', label: 'Roles', parentId: 'a1', route: '/administration/roles' }),
  nav({ kind: 'module', id: 'm1', code: 'noroute', parentId: 'a1', route: '' }), // no route → excluded
  nav({ kind: 'application', id: 'o1', code: 'other', route: '/other' }),
  nav({ id: 'x1', code: 'thing', parentId: 'o1', route: '/other/thing' }), // out of scope
];

describe('pathToPrefix / inScope', () => {
  it('converts a slash path to a dot-prefix and tolerates a trailing slash', () => {
    expect(pathToPrefix('administration/users')).toBe('administration.users');
    expect(pathToPrefix('administration/')).toBe('administration');
  });

  it('scopes on dot boundaries (no bare prefix false-positives)', () => {
    expect(inScope('administration.users', 'administration')).toBe(true);
    expect(inScope('administration', 'administration')).toBe(true);
    expect(inScope('administrationX', 'administration')).toBe(false);
    expect(inScope('other', 'administration')).toBe(false);
    expect(inScope('anything', '')).toBe(true);
  });
});

describe('buildComponentKeys', () => {
  it('joins parent codes into dotted keys', () => {
    const keyOf = buildComponentKeys(NODES);
    expect(keyOf.get('a1')).toBe('administration');
    expect(keyOf.get('s1')).toBe('administration.users');
    expect(keyOf.get('x1')).toBe('other.thing');
  });
});

describe('parseRegistry', () => {
  it('extracts string-literal keys (single or double quotes) and counts PAGE_KEYS entries', () => {
    const { keys, pageKeysOnly } = parseRegistry(`
      PageRegistry.register('administration.users', UsersPage);
      PageRegistry.register("administration.users.detail", DetailPage);
      PageRegistry.register(PAGE_KEYS.USERS_LIST, lazyWithRetry(() => import('x')));
    `);
    expect(keys).toEqual(['administration.users', 'administration.users.detail']);
    expect(pageKeysOnly).toBe(1);
  });
});

describe('resolvePagePermission', () => {
  it('prefers read, then view, then the bare key, else undefined', () => {
    expect(resolvePagePermission('a.b', new Set(['a.b.read', 'a.b.view']))).toBe('a.b.read');
    expect(resolvePagePermission('a.b', new Set(['a.b.view']))).toBe('a.b.view');
    expect(resolvePagePermission('a.b', new Set(['a.b']))).toBe('a.b');
    expect(resolvePagePermission('a.b', new Set(['x']))).toBeUndefined();
  });
});

describe('resolveEndpointPermission', () => {
  const set = new Set(['administration.users.read', 'administration.users.create']);

  it('maps the verb to an action and reconciles against the live set', () => {
    expect(resolveEndpointPermission('administration.users', 'GET', set)).toBe('administration.users.read');
    expect(resolveEndpointPermission('administration.users', 'POST', set)).toBe('administration.users.create');
    expect(resolveEndpointPermission('a.b', 'GET', new Set(['a.b.view']))).toBe('a.b.view');
  });

  it('falls back to the primary-action candidate when none is in the set, but stays gated', () => {
    expect(resolveEndpointPermission('a.b', 'DELETE', new Set())).toBe('a.b.delete');
    expect(resolveEndpointPermission(undefined, 'GET', set)).toBeUndefined();
  });
});

describe('correlateRoutes', () => {
  const keyOf = buildComponentKeys(NODES);
  const registry = new Set(['administration.users.detail', 'administration.users.create']);
  const perms = new Set(['administration.users.read', 'administration.roles.read']);
  const routes = correlateRoutes(NODES, keyOf, registry, perms, 'administration');

  it('keeps only in-scope navigable nodes, sorted by component key', () => {
    expect(routes.map((r) => r.componentKey)).toEqual([
      'administration', // app landing
      'administration.roles',
      'administration.users',
    ]);
    // "administration.noroute" excluded (no route); "other.*" excluded (out of scope)
  });

  it('derives views from the registry (base list + present suffixes) and resolves permission', () => {
    const users = routes.find((r) => r.componentKey === 'administration.users')!;
    expect(users.views).toEqual(['list', 'detail', 'create']);
    expect(users.permission).toBe('administration.users.read');
    expect(users.parentComponentKey).toBe('administration');

    const roles = routes.find((r) => r.componentKey === 'administration.roles')!;
    expect(roles.views).toEqual(['list']);
    expect(roles.permission).toBe('administration.roles.read');
  });
});

describe('correlateEndpoints', () => {
  it('drops out-of-scope endpoints, resolves permission + status, and sorts deterministically', () => {
    const parsed: ParsedEndpoint[] = [
      { controller: 'UsersController', method: 'POST', route: '/api/administration/users', navRoute: 'administration.users', okStatus: 201 },
      { controller: 'X', method: 'GET', route: '/api/other/x', navRoute: 'other.x' }, // out of scope
      { controller: 'UsersController', method: 'GET', route: '/api/administration/users', navRoute: 'administration.users' },
    ];
    const perms = new Set(['administration.users.read', 'administration.users.create']);
    const eps = correlateEndpoints(parsed, perms, 'administration');

    expect(eps.map((e) => `${e.method} ${e.permission}`)).toEqual([
      'GET administration.users.read',
      'POST administration.users.create',
    ]);
    expect(eps[1].okStatus).toBe(201);
  });
});
