import { Decimal } from '@prisma/client/runtime/library';
import { AgentsService } from './agents.service';
import { createPrismaMock } from '../../testing/prisma-mock';

describe('AgentsService', () => {
  const parentAgentId = 1n;
  const createdAgentId = 2n;

  const tx = {
    user: {
      create: jest.fn(),
      findUnique: jest.fn(),
      update: jest.fn(),
    },
    userAuth: {
      create: jest.fn(),
    },
    userPreference: {
      create: jest.fn(),
    },
    userInvite: {
      create: jest.fn(),
      findUnique: jest.fn(),
    },
    agentProfile: {
      create: jest.fn(),
      findUnique: jest.fn(),
    },
    agentClosure: {
      create: jest.fn(),
      findMany: jest.fn(),
    },
  };

  const prismaBase = {
    ...tx,
    agentProfile: {
      ...tx.agentProfile,
      update: jest.fn(),
    },
    user: {
      ...tx.user,
      updateMany: jest.fn(),
    },
  };
  const prisma = createPrismaMock(prismaBase) as typeof prismaBase & { $transaction: jest.Mock };
  prisma.$transaction.mockImplementation(async (arg: unknown) => {
    if (Array.isArray(arg)) {
      return Promise.all(arg);
    }
    return (arg as (client: typeof tx) => Promise<unknown>)(tx);
  });
  const auth = {
    hashPassword: jest.fn(),
  };
  const systemConfig = {
    getAgentHierarchySettings: jest.fn(),
    getAgentSuspendSettings: jest.fn(),
  };
  const network = {};
  const credit = {
    recalculateUsedCredit: jest.fn(),
  };

  let service: AgentsService;

  beforeEach(() => {
    jest.clearAllMocks();
    service = new AgentsService(
      prisma as never,
      auth as never,
      systemConfig as never,
      network as never,
      credit as never,
    );

    systemConfig.getAgentHierarchySettings.mockResolvedValue({
      maxAgentLevel: 3,
      defaultSubAgentCreditRatio: 50,
    });
    systemConfig.getAgentSuspendSettings.mockResolvedValue({
      suspendFreezeDirectPlayers: true,
      suspendBlockPlayerLogin: true,
    });
    auth.hashPassword.mockResolvedValue('hashed-password');
    prisma.agentProfile.findUnique.mockResolvedValue({
      userId: parentAgentId,
      level: 1,
      creditLimit: new Decimal(10000),
      usedCredit: new Decimal(2000),
      cashbackRate: new Decimal(10),
      maxSingleDeposit: null,
      maxDailyDeposit: null,
    });
    tx.user.create.mockResolvedValue({
      id: createdAgentId,
      username: 'agent-child',
      userType: 'AGENT',
    });
    tx.user.findUnique.mockResolvedValue(null);
    tx.user.update.mockResolvedValue({});
    tx.userInvite.findUnique.mockResolvedValue(null);
    tx.userInvite.create.mockResolvedValue({});
    tx.userAuth.create.mockResolvedValue({});
    tx.userPreference.create.mockResolvedValue({});
    tx.agentProfile.create.mockResolvedValue({});
    tx.agentClosure.create.mockResolvedValue({});
    tx.agentClosure.findMany.mockResolvedValue([
      { ancestorId: parentAgentId, depth: 0 },
    ]);
  });

  it('recalculates parent credit exposure after creating a child agent', async () => {
    await service.createAgent(99n, {
      username: 'agent-child',
      password: 'secret',
      level: 2,
      parentAgentId,
    });

    expect(tx.agentProfile.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({ creditLimit: 4000 }),
      }),
    );
    expect(credit.recalculateUsedCredit).toHaveBeenCalledWith(parentAgentId);
    expect(credit.recalculateUsedCredit).not.toHaveBeenCalledWith(createdAgentId);
  });

  it('applies global suspend defaults when suspending without explicit flags', async () => {
    jest.spyOn(service, 'getAgentAdminDetail').mockResolvedValue({ userId: '10' } as never);
    prisma.agentProfile.findUnique.mockResolvedValue({
      userId: 10n,
      user: { username: 'agent-a', locale: 'zh-CN' },
      parentAgentId: null,
    });
    prisma.user.updateMany.mockResolvedValue({ count: 2 });
    prisma.agentProfile.update.mockResolvedValue({});
    prisma.user.update.mockResolvedValue({});

    await service.updateAgentAdmin(10n, { status: 'SUSPENDED' });

    expect(systemConfig.getAgentSuspendSettings).toHaveBeenCalled();
    expect(prisma.user.updateMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ parentId: 10n, userType: 'PLAYER' }),
        data: { status: 'SUSPENDED' },
      }),
    );
    expect(prisma.agentProfile.update).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({ blockDirectPlayerLogin: true }),
      }),
    );
  });
});
