import { Result } from '@core/shared/domain/Result';
import { describe, expect, it, vi } from 'vitest';
import { PaymentsService } from './PaymentsService';

describe('PaymentsService', () => {
  const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };

  type MockUseCase = { execute: ReturnType<typeof vi.fn> };

  function makeService(overrides?: Partial<Record<string, MockUseCase>>) {
    const getPaymentsUseCase = overrides?.getPaymentsUseCase ?? { execute: vi.fn(async () => Result.ok({ payments: [] })) };
    const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ paymentId: 'p1' })) };
    const updatePaymentStatusUseCase =
      overrides?.updatePaymentStatusUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
    const getMembershipFeesUseCase =
      overrides?.getMembershipFeesUseCase ?? { execute: vi.fn(async () => Result.ok({ fee: null, payments: [] })) };
    const upsertMembershipFeeUseCase =
      overrides?.upsertMembershipFeeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
    const updateMemberPaymentUseCase =
      overrides?.updateMemberPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
    const getPrizesUseCase = overrides?.getPrizesUseCase ?? { execute: vi.fn(async () => Result.ok({ prizes: [] })) };
    const createPrizeUseCase = overrides?.createPrizeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
    const awardPrizeUseCase = overrides?.awardPrizeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
    const deletePrizeUseCase = overrides?.deletePrizeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
    const getWalletUseCase = overrides?.getWalletUseCase ?? { execute: vi.fn(async () => Result.ok({ balance: 0 })) };
    const processWalletTransactionUseCase =
      overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };

    const service = new PaymentsService(
      getPaymentsUseCase as never,
      createPaymentUseCase as never,
      updatePaymentStatusUseCase as never,
      getMembershipFeesUseCase as never,
      upsertMembershipFeeUseCase as never,
      updateMemberPaymentUseCase as never,
      getPrizesUseCase as never,
      createPrizeUseCase as never,
      awardPrizeUseCase as never,
      deletePrizeUseCase as never,
      getWalletUseCase as never,
      processWalletTransactionUseCase as never,
      logger as never,
    );

    return {
      service,
      getPaymentsUseCase,
      createPaymentUseCase,
      updatePaymentStatusUseCase,
      getMembershipFeesUseCase,
      upsertMembershipFeeUseCase,
      updateMemberPaymentUseCase,
      getPrizesUseCase,
      createPrizeUseCase,
      awardPrizeUseCase,
      deletePrizeUseCase,
      getWalletUseCase,
      processWalletTransactionUseCase,
    };
  }

  it('getPayments returns presenter model on success', async () => {
    const { service, getPaymentsUseCase } = makeService();
    await expect(service.getPayments({ leagueId: 'l1' } as never)).resolves.toEqual({ payments: [] });
    expect(getPaymentsUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
  });

  it('getPayments throws when use case returns error (code message)', async () => {
    const { service } = makeService({
      getPaymentsUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
    });
    await expect(service.getPayments({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
  });

  it('createPayment returns presenter model on success', async () => {
    const { service, createPaymentUseCase } = makeService();
    await expect(service.createPayment({ leagueId: 'l1' } as never)).resolves.toEqual({ paymentId: 'p1' });
    expect(createPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
  });

  it('createPayment throws when use case returns error', async () => {
    const { service } = makeService({
      createPaymentUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
    });
    await expect(service.createPayment({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
  });

  it('updatePaymentStatus returns presenter model on success', async () => {
    const { service, updatePaymentStatusUseCase } = makeService();
    await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).resolves.toEqual({ success: true });
    expect(updatePaymentStatusUseCase.execute).toHaveBeenCalledWith({ paymentId: 'p1' });
  });

  it('updatePaymentStatus throws when use case returns error', async () => {
    const { service } = makeService({
      updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
    });
    await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
  });

  it('getMembershipFees returns viewModel on success', async () => {
    const { service, getMembershipFeesUseCase } = makeService({
      getMembershipFeesUseCase: { execute: vi.fn(async () => Result.ok({ fee: { amount: 1 }, payments: [] })) }
    });

    await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as never)).resolves.toEqual({
      fee: { amount: 1 },
      payments: [],
    });
    expect(getMembershipFeesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', driverId: 'd1' });
  });

  it('getMembershipFees throws when use case returns error', async () => {
    const { service } = makeService({
      getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
    });
    await expect(service.getMembershipFees({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
  });

  it('upsertMembershipFee returns viewModel on success', async () => {
    const { service, upsertMembershipFeeUseCase } = makeService();

    await expect(service.upsertMembershipFee({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
    expect(upsertMembershipFeeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
  });

  it('upsertMembershipFee throws on error branch (defensive check)', async () => {
    const { service } = makeService({
      upsertMembershipFeeUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) },
    });

    await expect(service.upsertMembershipFee({ leagueId: 'l1' } as never)).rejects.toThrow(
      'Failed to upsert membership fee',
    );
  });

  it('updateMemberPayment returns viewModel on success', async () => {
    const { service, updateMemberPaymentUseCase } = makeService();

    await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
    expect(updateMemberPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
  });

  it('updateMemberPayment throws when use case returns error', async () => {
    const { service } = makeService({
      updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
    });

    await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
  });

  it('getPrizes maps seasonId optional', async () => {
    const getPrizesUseCase = { execute: vi.fn(async () => Result.ok({ prizes: [] })) };
    const { service } = makeService({
      getPrizesUseCase,
    });

    await expect(service.getPrizes({ leagueId: 'l1' } as never)).resolves.toEqual({ prizes: [] });
    expect(getPrizesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });

    await expect(service.getPrizes({ leagueId: 'l1', seasonId: 's1' } as never)).resolves.toEqual({ prizes: [] });
    expect(getPrizesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', seasonId: 's1' });
  });

  it('createPrize calls use case and returns viewModel', async () => {
    const createPrizeUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
    const { service } = makeService({
      createPrizeUseCase,
    });

    await expect(service.createPrize({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
    expect(createPrizeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
  });

  it('awardPrize calls use case and returns viewModel', async () => {
    const awardPrizeUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
    const { service } = makeService({
      awardPrizeUseCase,
    });

    await expect(service.awardPrize({ prizeId: 'p1' } as never)).resolves.toEqual({ success: true });
    expect(awardPrizeUseCase.execute).toHaveBeenCalledWith({ prizeId: 'p1' });
  });

  it('deletePrize calls use case and returns viewModel', async () => {
    const deletePrizeUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
    const { service } = makeService({
      deletePrizeUseCase,
    });

    await expect(service.deletePrize({ prizeId: 'p1' } as never)).resolves.toEqual({ success: true });
    expect(deletePrizeUseCase.execute).toHaveBeenCalledWith({ prizeId: 'p1' });
  });

  it('getWallet calls use case and returns viewModel', async () => {
    const getWalletUseCase = { execute: vi.fn(async () => Result.ok({ balance: 10 })) };
    const { service } = makeService({
      getWalletUseCase,
    });

    await expect(service.getWallet({ leagueId: 'l1' } as never)).resolves.toEqual({ balance: 10 });
    expect(getWalletUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
  });

  it('processWalletTransaction calls use case and returns viewModel', async () => {
    const processWalletTransactionUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
    const { service } = makeService({
      processWalletTransactionUseCase,
    });

    await expect(service.processWalletTransaction({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
    expect(processWalletTransactionUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
  });

  it('getPayments throws fallback message when error has no code', async () => {
    const { service } = makeService({
      getPaymentsUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
    });

    await expect(service.getPayments({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to get payments');
  });

  it('createPayment throws fallback message when error has no code', async () => {
    const { service } = makeService({
      createPaymentUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
    });

    await expect(service.createPayment({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to create payment');
  });

  it('updatePaymentStatus throws fallback message when error has no code', async () => {
    const { service } = makeService({
      updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
    });

    await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).rejects.toThrow('Failed to update payment status');
  });

  it('getMembershipFees throws fallback message when error has no code', async () => {
    const { service } = makeService({
      getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
    });

    await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as never)).rejects.toThrow('Failed to get membership fees');
  });

  it('updateMemberPayment throws fallback message when error has no code', async () => {
    const { service } = makeService({
      updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
    });

    await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to update member payment');
  });
});
