import { RecurringTransactionIntervalEnum } from '../recurring-transactions/recurring-transaction-interval.enum';
import { CashboxExportScheduler } from './cashbox-export.scheduler';

describe('CashboxExportScheduler', () => {
  const subscriptionRepository = { find: jest.fn(), save: jest.fn((v) => v) };
  const teamRepository = { findOne: jest.fn() };
  const mailService = { cashboxExport: jest.fn() };
  const logger = { info: jest.fn(), error: jest.fn() };
  let scheduler: CashboxExportScheduler;

  beforeEach(() => {
    jest.clearAllMocks();
    scheduler = new CashboxExportScheduler(
      subscriptionRepository as any,
      teamRepository as any,
      mailService as any,
      logger as any,
    );
  });

  it('does nothing when no subscription is due', async () => {
    subscriptionRepository.find.mockResolvedValue([]);

    await scheduler.runDueSubscriptions();

    expect(teamRepository.findOne).not.toHaveBeenCalled();
    expect(mailService.cashboxExport).not.toHaveBeenCalled();
  });

  it('emails the elapsed monthly period and advances nextRunDate', async () => {
    subscriptionRepository.find.mockResolvedValue([
      {
        id: 1,
        team: { id: 5 },
        recipients: ['vorstand@example.com'],
        interval: RecurringTransactionIntervalEnum.monthly,
        nextRunDate: '2026-09-01T00:00:00.000Z',
        active: true,
      },
    ]);
    teamRepository.findOne.mockResolvedValue({
      id: 5,
      name: 'Team A',
      alias: 'team-a',
      transactions: [
        { date: '2026-08-15T00:00:00.000Z', amount: 10, note: 'Sponsoring', type: { name: 'credit' } },
      ],
      players: [],
    });

    await scheduler.runDueSubscriptions();

    expect(teamRepository.findOne).toHaveBeenCalledWith({
      where: { id: 5 },
      relations: ['players', 'players.transactions', 'transactions'],
    });
    expect(mailService.cashboxExport).toHaveBeenCalledTimes(1);
    const [mailData, attachment, filename] = mailService.cashboxExport.mock.calls[0];
    expect(mailData).toEqual({
      to: 'vorstand@example.com',
      data: { teamName: 'Team A', from: '2026-08-01', to: '2026-08-31' },
    });
    expect(Buffer.isBuffer(attachment)).toBe(true);
    expect(filename).toBe('kassenbuch_team-a_2026-08-01_2026-08-31.pdf');
    expect(subscriptionRepository.save).toHaveBeenCalledWith(
      expect.objectContaining({ nextRunDate: '2026-10-01T00:00:00.000Z' }),
    );
    expect(logger.info).toHaveBeenCalledWith({
      event: 'cashbox_export_subscription_run',
      details: 'teamId=5 recipients=1 from=2026-08-01 to=2026-08-31',
      userId: -1,
    });
  });

  it.each([
    [RecurringTransactionIntervalEnum.monthly, '2026-09-01T00:00:00.000Z', '2026-08-01', '2026-08-31', '2026-10-01T00:00:00.000Z'],
    [RecurringTransactionIntervalEnum.quarterly, '2026-09-01T00:00:00.000Z', '2026-06-01', '2026-08-31', '2026-12-01T00:00:00.000Z'],
    [RecurringTransactionIntervalEnum.yearly, '2027-01-01T00:00:00.000Z', '2026-01-01', '2026-12-31', '2028-01-01T00:00:00.000Z'],
  ])(
    'computes the elapsed period and next run date for %s',
    async (interval, nextRunDate, expectedFrom, expectedTo, expectedNext) => {
      subscriptionRepository.find.mockResolvedValue([
        { id: 1, team: { id: 5 }, recipients: ['a@example.com'], interval, nextRunDate, active: true },
      ]);
      teamRepository.findOne.mockResolvedValue({
        id: 5,
        name: 'Team A',
        alias: 'team-a',
        transactions: [],
        players: [],
      });

      await scheduler.runDueSubscriptions();

      const [mailData] = mailService.cashboxExport.mock.calls[0];
      expect(mailData.data.from).toBe(expectedFrom);
      expect(mailData.data.to).toBe(expectedTo);
      expect(subscriptionRepository.save).toHaveBeenCalledWith(
        expect.objectContaining({ nextRunDate: expectedNext }),
      );
    },
  );

  it('processes multiple due subscriptions independently', async () => {
    subscriptionRepository.find.mockResolvedValue([
      { id: 1, team: { id: 5 }, recipients: ['a@example.com'], interval: RecurringTransactionIntervalEnum.monthly, nextRunDate: '2026-09-01T00:00:00.000Z', active: true },
      { id: 2, team: { id: 6 }, recipients: ['b@example.com'], interval: RecurringTransactionIntervalEnum.monthly, nextRunDate: '2026-09-01T00:00:00.000Z', active: true },
    ]);
    teamRepository.findOne.mockResolvedValue({
      id: 5,
      name: 'Team A',
      alias: 'team-a',
      transactions: [],
      players: [],
    });

    await scheduler.runDueSubscriptions();

    expect(mailService.cashboxExport).toHaveBeenCalledTimes(2);
  });

  it('continues processing when one subscription fails', async () => {
    subscriptionRepository.find.mockResolvedValue([
      { id: 1, team: { id: 5 }, recipients: ['a@example.com'], interval: RecurringTransactionIntervalEnum.monthly, nextRunDate: '2026-09-01T00:00:00.000Z', active: true },
      { id: 2, team: { id: 6 }, recipients: ['b@example.com'], interval: RecurringTransactionIntervalEnum.monthly, nextRunDate: '2026-09-01T00:00:00.000Z', active: true },
    ]);
    teamRepository.findOne.mockResolvedValue({
      id: 5,
      name: 'Team A',
      alias: 'team-a',
      transactions: [],
      players: [],
    });
    mailService.cashboxExport.mockRejectedValueOnce(new Error('smtp down'));

    await scheduler.runDueSubscriptions();

    expect(mailService.cashboxExport).toHaveBeenCalledTimes(2);
    expect(subscriptionRepository.save).toHaveBeenCalledTimes(1);
    expect(logger.error).toHaveBeenCalledWith({
      event: 'cashbox_export_subscription_run_fail',
      details: expect.stringContaining('subscriptionId=1 teamId=5'),
      userId: -1,
    });
  });
});
