import { Decimal } from '@prisma/client/runtime/library';
import { AgentCreditService } from './agent-credit.service';
import { expectAppError } from '../../testing/prisma-mock';

describe('AgentCreditService', () => {
  const tx = {
    $queryRaw: jest.fn(),
    user: {
      findFirst: jest.fn(),
      findMany: jest.fn(),
    },
    agentClosure: {
      findMany: jest.fn(),
    },
    agentProfile: {
      findUnique: jest.fn(),
      findMany: jest.fn(),
      update: jest.fn(),
    },
    walletTransaction: {
      findUnique: jest.fn(),
      aggregate: jest.fn(),
    },
  };
  const prisma = {
    ...tx,
    $transaction: jest.fn(async (fn: (client: typeof tx) => Promise<unknown>) => fn(tx)),
  };
  const funds = {
    deposit: jest.fn(),
    withdraw: jest.fn(),
  };

  let service: AgentCreditService;

  beforeEach(() => {
    jest.clearAllMocks();
    service = new AgentCreditService(prisma as never, funds as never);
    tx.walletTransaction.findUnique.mockResolvedValue(null);
    tx.user.findFirst.mockResolvedValue({ id: 10n, parentId: 1n, userType: 'PLAYER' });
    tx.user.findMany.mockResolvedValue([]);
    tx.agentClosure.findMany.mockResolvedValue([{ ancestorId: 1n, depth: 0 }]);
    tx.agentProfile.findMany.mockResolvedValue([]);
    tx.agentProfile.update.mockResolvedValue({});
    tx.$queryRaw.mockResolvedValue([
      {
        user_id: 1n,
        parent_agent_id: null,
        credit_limit: new Decimal(1000),
        used_credit: new Decimal(0),
        max_single_deposit: null,
        max_daily_deposit: new Decimal(100),
      },
    ]);
    tx.agentProfile.findUnique.mockResolvedValue({
      userId: 1n,
      parentAgentId: null,
      creditLimit: new Decimal(1000),
      usedCredit: new Decimal(0),
      maxSingleDeposit: null,
      maxDailyDeposit: new Decimal(100),
    });
    tx.walletTransaction.aggregate.mockResolvedValue({ _sum: { amount: new Decimal(80) } });
  });

  it('counts prior AGENT_DEPOSIT postings when enforcing the daily top-up limit', async () => {
    await expect(
      service.depositToPlayer(1n, 10n, 30, 'REQ-DAILY-LIMIT'),
    ).rejects.toMatchObject(expectAppError('AGENT_DAILY_TOPUP_LIMIT'));

    expect(tx.walletTransaction.aggregate).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          operatorId: 1n,
          transactionType: { in: ['AGENT_DEPOSIT', 'MANUAL_DEPOSIT'] },
        }),
      }),
    );
    expect(funds.deposit).not.toHaveBeenCalled();
  });

  it('recalculates the changed agent and every ancestor in closure order', async () => {
    tx.agentClosure.findMany.mockResolvedValue([
      { ancestorId: 3n, depth: 0 },
      { ancestorId: 2n, depth: 1 },
      { ancestorId: 1n, depth: 2 },
    ]);

    await service.recalculateUsedCredit(3n);

    expect(tx.agentProfile.update.mock.calls.map(([arg]) => arg.where.userId)).toEqual([
      3n,
      2n,
      1n,
    ]);
  });
});
