import { describe, it, expect } from 'vitest';
import { validate, validateBusinessRules } from '../validate.js';
import type { CoreSeedSpec } from '../types.js';

function baseSpec(): CoreSeedSpec {
  return {
    appCode: 'TestV2',
    namespace: undefined,
    applications: [
      {
        code: 'crm',
        modules: ['contacts'],
        navigation: [
          { level: 'application', code: 'crm', label: 'CRM', icon: 'Building', iconType: 'Lucide', route: '/crm', displayOrder: 1 },
          { level: 'module', code: 'contacts', parentCode: 'crm', label: 'Contacts', icon: 'Users', iconType: 'Lucide', route: '/crm/contacts', displayOrder: 1 },
          { level: 'section', code: 'directory', parentCode: 'contacts', label: 'Annuaire', icon: 'Circle', iconType: 'Lucide', route: '/crm/contacts/directory', displayOrder: 1 },
        ],
        roles: [{ code: 'admin', name: 'Administrator', applicationCode: 'crm', isDefault: false }],
        permissions: [
          { path: 'crm.contacts.directory.access', action: 'access', sectionCode: 'directory' },
          { path: 'crm.contacts.directory.read', action: 'read', sectionCode: 'directory' },
        ],
        rolePermissions: [],
        testUsers: [],
      },
    ],
    testUserPassword: 'DevTest!2026Secure',
    testUserEmailDomain: 'smartstack.local',
    emitDevCredentialsManifest: true,
    projectPath: '/tmp/x',
  };
}

function patchApp(spec: CoreSeedSpec, mut: (a: CoreSeedSpec['applications'][number]) => void): CoreSeedSpec {
  const next = { ...spec, applications: spec.applications.map((a) => ({ ...a })) };
  mut(next.applications[0]);
  return next;
}

describe('validate (scaffold-core-seed)', () => {
  it('passes the canonical happy-path fixture', () => {
    const result = validate(baseSpec());
    expect(result.valid).toBe(true);
    expect(result.errors).toEqual([]);
  });

  it('rejects a spec where modules[] declares a code with no matching nav module', () => {
    const spec = patchApp(baseSpec(), (a) => { a.modules = ['contacts', 'budgeting']; });
    const result = validateBusinessRules(spec);
    expect(result.valid).toBe(false);
    expect(result.errors.some((e) => e.includes('budgeting'))).toBe(true);
  });

  it('rejects a spec with no roles for the application', () => {
    const spec = patchApp(baseSpec(), (a) => { a.roles = []; });
    const result = validateBusinessRules(spec);
    expect(result.valid).toBe(false);
    expect(result.errors.some((e) => e.includes('no roles defined'))).toBe(true);
  });

  it('rejects a module with < 2 permissions (the access + read floor)', () => {
    const spec = patchApp(baseSpec(), (a) => { a.permissions = a.permissions.slice(0, 1); });
    const result = validateBusinessRules(spec);
    expect(result.valid).toBe(false);
    expect(result.errors.some((e) => e.includes('contacts') && e.includes('1 permission'))).toBe(true);
  });

  it('warns on permissions referencing unknown sections without blocking', () => {
    const spec = patchApp(baseSpec(), (a) => {
      a.permissions.push({
        path: 'crm.contacts.unknown.read',
        action: 'read',
        sectionCode: 'unknown',
      });
    });
    const result = validateBusinessRules(spec);
    expect(result.valid).toBe(true);
    expect(result.warnings.some((w) => w.includes('unknown section'))).toBe(true);
  });

  it('rejects a test user referencing a missing role (hard error — Phase 5 needs login)', () => {
    const spec = patchApp(baseSpec(), (a) => { a.testUsers = [{ roleCode: 'ghost' }]; });
    const result = validateBusinessRules(spec);
    expect(result.valid).toBe(false);
    expect(result.errors.some((e) => e.includes('ghost'))).toBe(true);
  });

  it('rejects when application code does not match the application-level nav entry', () => {
    const spec = patchApp(baseSpec(), (a) => { a.code = 'wrong'; });
    const result = validateBusinessRules(spec);
    expect(result.valid).toBe(false);
    expect(result.errors.some((e) => e.includes('application-level navigation code'))).toBe(true);
  });

  it('rejects duplicate application codes across spec.applications[]', () => {
    const spec = baseSpec();
    spec.applications = [...spec.applications, { ...spec.applications[0] }];
    const result = validateBusinessRules(spec);
    expect(result.valid).toBe(false);
    expect(result.errors.some((e) => e.includes('Duplicate application code'))).toBe(true);
  });

  it('validates each application independently and reports each issue with [applications[code]] prefix', () => {
    const spec = baseSpec();
    spec.applications = [
      spec.applications[0],
      {
        code: 'budgeting',
        modules: ['budget'],
        navigation: [
          { level: 'application', code: 'budgeting', label: 'Budget', icon: 'Wallet', iconType: 'Lucide', route: '/budgeting', displayOrder: 1 },
          { level: 'module', code: 'budget', parentCode: 'budgeting', label: 'Budget', icon: 'PieChart', iconType: 'Lucide', route: '/budgeting/budget', displayOrder: 1 },
        ],
        // Missing roles → should fail per-app rule 3
        roles: [],
        permissions: [],
        rolePermissions: [],
        testUsers: [],
      },
    ];
    const result = validateBusinessRules(spec);
    expect(result.valid).toBe(false);
    expect(result.errors.some((e) => e.includes('applications[budgeting]') && e.includes('no roles defined'))).toBe(true);
    expect(result.errors.some((e) => e.includes('applications[crm]'))).toBe(false);
  });
});
