import { describe, it, expect } from 'vitest';
import { buildCoreSeedSpec, type BuildCoreSeedSpecInput } from '../build-spec.js';

function baseInput(): BuildCoreSeedSpecInput {
  return {
    appCode: 'TestV2',
    projectPath: '/tmp/x',
    applications: [{ code: 'crm', label: 'CRM' }],
    modules: [
      { code: 'contacts', label: 'Contacts', applicationCode: 'crm' },
      { code: 'budgeting', label: 'Budget', applicationCode: 'crm' },
    ],
    sections: [
      { code: 'directory', label: 'Annuaire', moduleCode: 'contacts' },
      { code: 'overview', label: 'Vue d\'ensemble', moduleCode: 'budgeting' },
    ],
    resources: [],
    actors: [
      { code: 'admin', name: 'Administrator', applicationCode: 'crm' },
      { code: 'commercial', name: 'Commercial', applicationCode: 'crm', isDefault: true },
    ],
    permissions: [
      { path: 'crm.contacts.directory.access', roles: ['admin'] },
      { path: 'crm.contacts.directory.read', roles: ['admin', 'commercial'] },
      { path: 'crm.budgeting.overview.access', roles: ['admin'] },
      { path: 'crm.budgeting.overview.read', roles: ['admin'] },
    ],
  };
}

function multiAppInput(): BuildCoreSeedSpecInput {
  return {
    appCode: 'TestV2',
    projectPath: '/tmp/x',
    applications: [
      { code: 'crm', label: 'CRM' },
      { code: 'budgeting', label: 'Budget' },
    ],
    modules: [
      { code: 'contacts', label: 'Contacts', applicationCode: 'crm' },
      { code: 'budget', label: 'Budget', applicationCode: 'budgeting' },
    ],
    sections: [
      { code: 'directory', label: 'Annuaire', moduleCode: 'contacts' },
      { code: 'overview', label: 'Vue d\'ensemble', moduleCode: 'budget' },
    ],
    resources: [],
    actors: [
      { code: 'admin', name: 'Administrator', applicationCode: 'crm' },
      { code: 'controller', name: 'Controller', applicationCode: 'budgeting' },
    ],
    permissions: [
      { path: 'crm.contacts.directory.access', roles: ['admin'] },
      { path: 'crm.contacts.directory.read', roles: ['admin'] },
      { path: 'budgeting.budget.overview.access', roles: ['controller'] },
      { path: 'budgeting.budget.overview.read', roles: ['controller'] },
    ],
  };
}

describe('buildCoreSeedSpec', () => {
  it('produces one ApplicationSpec per declared application', () => {
    const spec = buildCoreSeedSpec(baseInput());
    expect(spec.applications).toHaveLength(1);
    expect(spec.applications[0].code).toBe('crm');
  });

  it('flattens applications + modules + sections + resources into the per-app navigation array', () => {
    const spec = buildCoreSeedSpec(baseInput());
    const levels = spec.applications[0].navigation.map((n) => n.level);
    expect(levels).toEqual(['application', 'module', 'module', 'section', 'section']);
  });

  it('extracts modules[] for the gate from input.modules (deduped)', () => {
    const input = baseInput();
    input.modules.push({ code: 'contacts', label: 'Contacts (dup)', applicationCode: 'crm' });
    const spec = buildCoreSeedSpec(input);
    expect(spec.applications[0].modules).toEqual(['contacts', 'budgeting']);
  });

  it('parses 4-segment permission paths into action + sectionCode', () => {
    const spec = buildCoreSeedSpec(baseInput());
    expect(spec.applications[0].permissions).toHaveLength(4);
    expect(spec.applications[0].permissions[0]).toEqual({
      path: 'crm.contacts.directory.access',
      action: 'access',
      sectionCode: 'directory',
    });
  });

  it('expands each permission.roles[] into a role-permission mapping (per app)', () => {
    const spec = buildCoreSeedSpec(baseInput());
    const directoryRead = spec.applications[0].rolePermissions.filter(
      (rp) => rp.permissionPath === 'crm.contacts.directory.read',
    );
    expect(directoryRead).toHaveLength(2);
    expect(directoryRead.map((rp) => rp.roleCode).sort()).toEqual(['admin', 'commercial']);
  });

  it('drops permissions with malformed paths instead of throwing', () => {
    const input = baseInput();
    input.permissions.push({ path: 'broken.path', roles: ['admin'] });
    const spec = buildCoreSeedSpec(input);
    expect(spec.applications[0].permissions.find((p) => p.path === 'broken.path')).toBeUndefined();
  });

  it('emits one test user per actor (Phase 5 RBAC needs one per role)', () => {
    const spec = buildCoreSeedSpec(baseInput());
    expect(spec.applications[0].testUsers).toHaveLength(2);
    expect(spec.applications[0].testUsers.map((tu) => tu.roleCode)).toEqual(['admin', 'commercial']);
  });

  it('defaults icon/iconType/route fields when the input is sparse', () => {
    const spec = buildCoreSeedSpec(baseInput());
    const moduleEntry = spec.applications[0].navigation.find((n) => n.code === 'contacts')!;
    expect(moduleEntry.icon).toBe('Circle');
    expect(moduleEntry.iconType).toBe('Lucide');
    expect(moduleEntry.route).toBe('/crm/contacts');
  });

  it('passes through testUserPassword and testUserEmailDomain overrides', () => {
    const input = { ...baseInput(), testUserPassword: 'CustomPass!2026', testUserEmailDomain: 'example.local' };
    const spec = buildCoreSeedSpec(input);
    expect(spec.testUserPassword).toBe('CustomPass!2026');
    expect(spec.testUserEmailDomain).toBe('example.local');
  });

  it('groups modules + actors + permissions by their owning application (multi-app)', () => {
    const spec = buildCoreSeedSpec(multiAppInput());
    expect(spec.applications).toHaveLength(2);

    const crm = spec.applications.find((a) => a.code === 'crm')!;
    const budgeting = spec.applications.find((a) => a.code === 'budgeting')!;

    expect(crm.modules).toEqual(['contacts']);
    expect(crm.roles.map((r) => r.code)).toEqual(['admin']);
    expect(crm.permissions.map((p) => p.path)).toEqual([
      'crm.contacts.directory.access',
      'crm.contacts.directory.read',
    ]);

    expect(budgeting.modules).toEqual(['budget']);
    expect(budgeting.roles.map((r) => r.code)).toEqual(['controller']);
    expect(budgeting.permissions.map((p) => p.path)).toEqual([
      'budgeting.budget.overview.access',
      'budgeting.budget.overview.read',
    ]);
  });

  it('does not leak permissions across apps when section codes happen to repeat across apps', () => {
    // Edge case: two apps both have a section called "overview". The builder
    // must use the section→module→app chain to attribute permissions correctly,
    // not just the section code.
    const input = multiAppInput();
    input.sections.push({ code: 'overview', label: 'CRM Overview', moduleCode: 'contacts' });
    input.permissions.push({ path: 'crm.contacts.overview.read', roles: ['admin'] });
    const spec = buildCoreSeedSpec(input);
    const crm = spec.applications.find((a) => a.code === 'crm')!;
    const budgeting = spec.applications.find((a) => a.code === 'budgeting')!;
    expect(crm.permissions.find((p) => p.path === 'crm.contacts.overview.read')).toBeDefined();
    expect(budgeting.permissions.find((p) => p.path === 'crm.contacts.overview.read')).toBeUndefined();
  });
});
