/**
 * lib/sql-discovery — pure-layer tests (row parsing + tree nesting + grant grouping).
 */
import { describe, it, expect } from 'vitest';
import {
  parseNavRows,
  parsePermissionRows,
  parseRolePermissionRows,
  buildNavTree,
  groupGrantsByRole,
} from '../sql-discovery.js';
import { parsePipeRows } from '../sqlcmd.js';

const NAV_ROWS = [
  ['APP', 'a1', 'administration', 'Administration', '', '/administration', '1'],
  ['MOD', 'm1', 'users', 'Users', 'a1', '/administration/users', '0'],
  ['SEC', 's1', 'list', 'List', 'm1', '/administration/users/list', '0'],
  ['RES', 'r1', 'detail', 'Detail', 's1', '/administration/users/detail', '0'],
  ['XXX', 'bad', 'noise', '', '', '', '0'], // unknown type ignored
];

describe('parseNavRows', () => {
  it('maps known TYPE prefixes to kinds and reads the personal flag for apps only', () => {
    const nodes = parseNavRows(NAV_ROWS);
    expect(nodes.map((n) => n.kind)).toEqual(['application', 'module', 'section', 'resource']);
    expect(nodes[0]).toMatchObject({ id: 'a1', code: 'administration', parentId: '', isPersonal: true });
    expect(nodes[1]).toMatchObject({ kind: 'module', parentId: 'a1', isPersonal: false });
  });

  it('parses from raw pipe output end-to-end', () => {
    const raw = 'APP|a1|administration|Administration||/administration|0';
    const nodes = parseNavRows(parsePipeRows(raw));
    expect(nodes).toHaveLength(1);
    expect(nodes[0].isPersonal).toBe(false);
  });
});

describe('buildNavTree', () => {
  it('nests modules/sections/resources under their parents', () => {
    const tree = buildNavTree(parseNavRows(NAV_ROWS));
    expect(tree).toHaveLength(1);
    const app = tree[0];
    expect(app.kind).toBe('application');
    expect(app.children).toHaveLength(1);
    expect(app.children[0].kind).toBe('module');
    expect(app.children[0].children[0].kind).toBe('section');
    expect(app.children[0].children[0].children[0].kind).toBe('resource');
  });

  it('treats a node with an unknown parent as a root (no orphans dropped)', () => {
    const orphan = parseNavRows([['MOD', 'm9', 'x', 'X', 'missing', '/x', '0']]);
    expect(buildNavTree(orphan)).toHaveLength(1);
  });
});

describe('permissions + role grants', () => {
  it('parses permission rows with the wildcard flag', () => {
    const perms = parsePermissionRows([
      ['p1', 'administration.users.read', 'read', '1', '0'],
      ['p2', 'administration.users', '', '0', '1'],
    ]);
    expect(perms[0]).toMatchObject({ path: 'administration.users.read', isWildcard: false });
    expect(perms[1].isWildcard).toBe(true);
  });

  it('groups role grants by role name into the rbac-matrix grant shape', () => {
    const grants = parseRolePermissionRows([
      ['r1', 'admin', 'core', '1', 'p2', 'administration.users', '1'],
      ['r1', 'admin', 'core', '1', 'p3', 'administration.roles.read', '0'],
      ['r2', 'viewer', 'core', '0', 'p4', 'administration.users.read', '0'],
    ]);
    const byRole = groupGrantsByRole(grants);
    expect(Object.keys(byRole).sort()).toEqual(['admin', 'viewer']);
    expect(byRole.admin).toEqual([
      { path: 'administration.users', isWildcard: true },
      { path: 'administration.roles.read', isWildcard: false },
    ]);
    expect(byRole.viewer).toEqual([{ path: 'administration.users.read', isWildcard: false }]);
  });
});
