import {
  ProvisioningNode,
  acceptsPayments,
  ancestorsOf,
  canTransitionTo,
  childLevelOf,
  childrenOf,
} from './provisioning-node.model';

function node(id: string, parentId: string | null, path: string[], displayName = id): ProvisioningNode {
  return {
    id,
    level: 'store',
    parentId,
    companyId: 'cmp_1',
    path,
    environment: 'TEST',
    status: 'active',
    externalReference: `ext-${id}`,
    displayName,
    createdAt: '2026-08-18T00:00:00.000Z',
    updatedAt: '2026-08-18T00:00:00.000Z',
  };
}

describe('provisioning node model', () => {
  describe('childLevelOf', () => {
    it('walks the four fixed levels in order', () => {
      expect(childLevelOf('company')).toBe('project');
      expect(childLevelOf('project')).toBe('store');
      expect(childLevelOf('store')).toBe('payment_point');
    });

    it('returns null at the leaf, so nothing can be nested below a payment point', () => {
      expect(childLevelOf('payment_point')).toBeNull();
    });
  });

  describe('acceptsPayments', () => {
    it('is true only for active nodes', () => {
      expect(acceptsPayments({ ...node('a', null, ['a']), status: 'active' })).toBeTrue();
      expect(acceptsPayments({ ...node('a', null, ['a']), status: 'suspended' })).toBeFalse();
      expect(acceptsPayments({ ...node('a', null, ['a']), status: 'disabled' })).toBeFalse();
    });
  });

  describe('canTransitionTo', () => {
    it('treats disabled as terminal', () => {
      expect(canTransitionTo('disabled', 'active')).toBeFalse();
      expect(canTransitionTo('disabled', 'suspended')).toBeFalse();
    });

    it('allows suspend and reactivate', () => {
      expect(canTransitionTo('active', 'suspended')).toBeTrue();
      expect(canTransitionTo('suspended', 'active')).toBeTrue();
    });

    it('rejects a no-op transition', () => {
      expect(canTransitionTo('active', 'active')).toBeFalse();
    });
  });

  describe('ancestorsOf', () => {
    it('resolves the root-to-leaf chain in path order', () => {
      const all = [
        node('cmp', null, ['cmp']),
        node('prj', 'cmp', ['cmp', 'prj']),
        node('str', 'prj', ['cmp', 'prj', 'str']),
      ];

      const chain = ancestorsOf(all[2], all);

      expect(chain.map(n => n.id)).toEqual(['cmp', 'prj', 'str']);
    });

    it('skips ids the caller did not supply rather than returning holes', () => {
      const leaf = node('str', 'prj', ['cmp', 'prj', 'str']);

      const chain = ancestorsOf(leaf, [leaf]);

      expect(chain.map(n => n.id)).toEqual(['str']);
    });
  });

  describe('childrenOf', () => {
    it('returns direct children only, not deeper descendants', () => {
      const all = [
        node('cmp', null, ['cmp']),
        node('prj', 'cmp', ['cmp', 'prj']),
        node('str', 'prj', ['cmp', 'prj', 'str']),
      ];

      expect(childrenOf('cmp', all).map(n => n.id)).toEqual(['prj']);
    });

    it('orders by displayName so the tree renders stably', () => {
      const all = [
        node('a', 'root', ['root', 'a'], 'Zebra'),
        node('b', 'root', ['root', 'b'], 'Alpha'),
      ];

      expect(childrenOf('root', all).map(n => n.displayName)).toEqual(['Alpha', 'Zebra']);
    });
  });
});
