import { AgentNetworkService } from './agent-network.service';

describe('AgentNetworkService', () => {
  let prisma: {
    agentClosure: { findMany: jest.Mock };
    agentProfile: { findUnique: jest.Mock; findMany: jest.Mock };
    user: { findMany: jest.Mock; findFirst: jest.Mock };
  };
  let service: AgentNetworkService;

  beforeEach(() => {
    prisma = {
      agentClosure: { findMany: jest.fn() },
      agentProfile: {
        findUnique: jest.fn(),
        findMany: jest.fn(),
      },
      user: {
        findMany: jest.fn(),
        findFirst: jest.fn(),
      },
    };
    service = new AgentNetworkService(prisma as never);
  });

  it('resolves portal scope from AgentClosure rows', async () => {
    prisma.agentProfile.findUnique.mockResolvedValue({ userId: 1n, level: 2 });
    prisma.agentClosure.findMany.mockResolvedValue([
      { descendantId: 1n, depth: 0 },
      { descendantId: 2n, depth: 1 },
      { descendantId: 4n, depth: 1 },
      { descendantId: 3n, depth: 2 },
    ]);

    const scope = await service.resolveScope(1n);

    expect(scope.rootAgentId).toBe(1n);
    expect(scope.rootLevel).toBe(2);
    expect(scope.subtreeIds).toEqual([1n, 2n, 4n, 3n]);
    expect(scope.descendantIds).toEqual([2n, 4n, 3n]);
    expect(scope.directChildIds).toEqual([2n, 4n]);
    expect(scope.subtreeIdSet.has('3')).toBe(true);
    expect(prisma.agentProfile.findMany).not.toHaveBeenCalled();
  });

  it('falls back to AgentProfile adjacency when closure rows are absent', async () => {
    prisma.agentProfile.findUnique.mockResolvedValue({ userId: 1n, level: 1 });
    prisma.agentClosure.findMany.mockResolvedValue([]);
    prisma.agentProfile.findMany.mockImplementation(({ where }: { where: { parentAgentId: bigint } }) => {
      const childrenByParent = new Map<bigint, Array<{ userId: bigint }>>([
        [1n, [{ userId: 2n }, { userId: 4n }]],
        [2n, [{ userId: 3n }]],
        [3n, []],
        [4n, []],
      ]);
      return Promise.resolve(childrenByParent.get(where.parentAgentId) ?? []);
    });

    const scope = await service.resolveScope(1n);

    expect(scope.subtreeIds).toEqual([1n, 2n, 4n, 3n]);
    expect(scope.descendantIds).toEqual([2n, 4n, 3n]);
    expect(scope.directChildIds).toEqual([2n, 4n]);
  });

  it('rejects agent outside descendant scope', async () => {
    prisma.agentClosure.findMany.mockResolvedValue([
      { descendantId: 1n, depth: 0 },
      { descendantId: 2n, depth: 1 },
    ]);

    await expect(service.assertDescendantAgent(1n, 9n)).rejects.toMatchObject({
      response: expect.objectContaining({ code: 'NOT_SUB_AGENT' }),
    });
    expect(prisma.agentProfile.findUnique).not.toHaveBeenCalled();
  });

  it('returns direct child profile for matching parent', async () => {
    const profile = { userId: 2n, parentAgentId: 1n };
    prisma.agentProfile.findUnique.mockResolvedValue(profile);

    await expect(service.assertDirectChildAgent(1n, 2n)).resolves.toBe(profile);
  });

  it('rejects non-direct child agent', async () => {
    prisma.agentProfile.findUnique.mockResolvedValue({ userId: 2n, parentAgentId: 9n });

    await expect(service.assertDirectChildAgent(1n, 2n)).rejects.toMatchObject({
      response: expect.objectContaining({ code: 'NOT_SUB_AGENT' }),
    });
  });
});
