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

function stubDeps() {
  return {
    users: {},
    agents: { updateAgentAdmin: jest.fn() } as AgentsService | { updateAgentAdmin: jest.Mock },
    wallet: {},
    matches: {},
    catalogArchive: {},
    outright: {},
    markets: {},
    settlement: {},
    cashback: {},
    content: { create: jest.fn() },
    i18n: {},
    audit: { log: jest.fn().mockResolvedValue(undefined) },
    bets: {},
    prisma: {},
    dashboardService: {},
    systemConfig: { getInboxNotifySettings: jest.fn() },
    bettingLimits: {},
    databaseReset: {},
    smokeTests: {},
    depositService: {},
    playerMessages: {
      broadcastBannerPromotion: jest.fn(),
      broadcastAnnouncementPromotion: jest.fn(),
    },
    staff: {},
    presence: {},
    depositCleanup: {},
  };
}

function buildController(deps: ReturnType<typeof stubDeps>) {
  return new AdminController(
    deps.users as never,
    deps.agents as never,
    deps.wallet as never,
    deps.matches as never,
    deps.catalogArchive as never,
    deps.outright as never,
    deps.markets as never,
    deps.settlement as never,
    deps.cashback as never,
    deps.content as never,
    deps.i18n as never,
    deps.audit as never,
    deps.bets as never,
    deps.prisma as never,
    deps.dashboardService as never,
    deps.systemConfig as never,
    deps.bettingLimits as never,
    deps.databaseReset as never,
    deps.smokeTests as never,
    deps.depositService as never,
    deps.playerMessages as never,
    deps.staff as never,
    deps.presence as never,
    deps.depositCleanup as never,
  );
}

describe('AdminController createContent inbox notify gating', () => {
  const deps = stubDeps();
  const controller = buildController(deps);

  const baseDto = {
    contentType: 'BANNER' as const,
    status: 'ACTIVE' as const,
    notifyInbox: true,
    translations: [{ locale: 'zh-CN', title: 'Promo', body: 'Body' }],
  };

  beforeEach(() => {
    jest.clearAllMocks();
    deps.content.create.mockResolvedValue({ id: '88' });
    deps.playerMessages.broadcastBannerPromotion.mockResolvedValue(3);
    deps.playerMessages.broadcastAnnouncementPromotion.mockResolvedValue(5);
  });

  it('does not broadcast banner when inbox.notify.banner is false', async () => {
    deps.systemConfig.getInboxNotifySettings.mockResolvedValue({
      inboxEnabled: true,
      deposit: true,
      banner: false,
      announcement: true,
    });

    const res = await controller.createContent(baseDto);

    expect(deps.playerMessages.broadcastBannerPromotion).not.toHaveBeenCalled();
    expect((res.data as { notifiedCount?: number }).notifiedCount).toBeUndefined();
  });

  it('broadcasts banner when inbox.notify.banner is true', async () => {
    deps.systemConfig.getInboxNotifySettings.mockResolvedValue({
      inboxEnabled: true,
      deposit: true,
      banner: true,
      announcement: false,
    });

    const res = await controller.createContent(baseDto);

    expect(deps.playerMessages.broadcastBannerPromotion).toHaveBeenCalledWith({
      contentId: 88n,
      translations: [{ locale: 'zh-CN', title: 'Promo', body: 'Body' }],
    });
    expect((res.data as { notifiedCount?: number }).notifiedCount).toBe(3);
  });

  it('does not broadcast announcement when inbox.notify.announcement is false', async () => {
    deps.systemConfig.getInboxNotifySettings.mockResolvedValue({
      inboxEnabled: true,
      deposit: true,
      banner: true,
      announcement: false,
    });

    await controller.createContent({
      ...baseDto,
      contentType: 'NOTICE',
    });

    expect(deps.playerMessages.broadcastAnnouncementPromotion).not.toHaveBeenCalled();
  });

  it('skips notify when inbox feature is disabled even if notifyInbox is checked', async () => {
    deps.systemConfig.getInboxNotifySettings.mockResolvedValue({
      inboxEnabled: false,
      deposit: true,
      banner: true,
      announcement: true,
    });

    await controller.createContent(baseDto);

    expect(deps.playerMessages.broadcastBannerPromotion).not.toHaveBeenCalled();
  });
});

describe('AdminController updateAgent suspend wiring', () => {
  const agentId = 10n;
  const operatorId = 1n;

  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 };

  const systemConfig = {
    getAgentHierarchySettings: jest.fn(),
    getAgentSuspendSettings: jest.fn(),
  };
  const audit = { log: jest.fn().mockResolvedValue(undefined) };

  let agentsService: AgentsService;
  let controller: AdminController;

  beforeEach(() => {
    jest.clearAllMocks();
    prisma.$transaction.mockImplementation(async (arg: unknown) => {
      if (Array.isArray(arg)) return Promise.all(arg);
      return (arg as (client: typeof tx) => Promise<unknown>)(tx);
    });

    systemConfig.getAgentSuspendSettings.mockResolvedValue({
      suspendFreezeDirectPlayers: true,
      suspendBlockPlayerLogin: true,
    });

    agentsService = new AgentsService(
      prisma as never,
      { hashPassword: jest.fn() } as never,
      systemConfig as never,
      {} as never,
      { recalculateUsedCredit: jest.fn() } as never,
    );
    jest.spyOn(agentsService, 'getAgentAdminDetail').mockResolvedValue({ userId: '10' } as never);

    prisma.agentProfile.findUnique.mockResolvedValue({
      userId: agentId,
      user: { username: 'agent-a', locale: 'zh-CN' },
      parentAgentId: null,
    });
    prisma.user.updateMany.mockResolvedValue({ count: 2 });
    prisma.agentProfile.update.mockResolvedValue({});
    prisma.user.update.mockResolvedValue({});

    const deps = stubDeps();
    deps.agents = agentsService as never;
    deps.audit = audit;
    controller = buildController(deps);
  });

  it('forwards bare SUSPENDED body to AgentsService and applies global defaults', async () => {
    await controller.updateAgent(operatorId, agentId.toString(), { status: 'SUSPENDED' });

    expect(systemConfig.getAgentSuspendSettings).toHaveBeenCalled();
    expect(prisma.user.updateMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ parentId: agentId, userType: 'PLAYER' }),
        data: { status: 'SUSPENDED' },
      }),
    );
    expect(prisma.agentProfile.update).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({ blockDirectPlayerLogin: true }),
      }),
    );
    expect(audit.log).toHaveBeenCalledWith(
      expect.objectContaining({
        operatorId,
        action: 'UPDATE_AGENT',
        module: 'AGENTS',
        targetId: agentId.toString(),
      }),
    );
  });

  it('does not freeze players when global default is false and body omits flag', async () => {
    systemConfig.getAgentSuspendSettings.mockResolvedValue({
      suspendFreezeDirectPlayers: false,
      suspendBlockPlayerLogin: false,
    });

    await controller.updateAgent(operatorId, agentId.toString(), { status: 'SUSPENDED' });

    expect(prisma.user.updateMany).not.toHaveBeenCalled();
    expect(prisma.agentProfile.update).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({ blockDirectPlayerLogin: false }),
      }),
    );
  });
});
