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

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

  function makeService(overrides?: Partial<Record<string, any>>) {
    const getPaymentsUseCase = overrides?.getPaymentsUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const updatePaymentStatusUseCase =
      overrides?.updatePaymentStatusUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const getMembershipFeesUseCase =
      overrides?.getMembershipFeesUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const upsertMembershipFeeUseCase =
      overrides?.upsertMembershipFeeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const updateMemberPaymentUseCase =
      overrides?.updateMemberPaymentUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const getPrizesUseCase = overrides?.getPrizesUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const createPrizeUseCase = overrides?.createPrizeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const awardPrizeUseCase = overrides?.awardPrizeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const deletePrizeUseCase = overrides?.deletePrizeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const getWalletUseCase = overrides?.getWalletUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
    const processWalletTransactionUseCase =
      overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };

    const getPaymentsPresenter = overrides?.getPaymentsPresenter ?? { getResponseModel: vi.fn(() => ({ payments: [] })) };
    const createPaymentPresenter =
      overrides?.createPaymentPresenter ?? { getResponseModel: vi.fn(() => ({ paymentId: 'p1' })) };
    const updatePaymentStatusPresenter =
      overrides?.updatePaymentStatusPresenter ?? { getResponseModel: vi.fn(() => ({ success: true })) };

    const getMembershipFeesPresenter = overrides?.getMembershipFeesPresenter ?? { viewModel: { fee: null, payments: [] } };
    const upsertMembershipFeePresenter = overrides?.upsertMembershipFeePresenter ?? { viewModel: { success: true } };
    const updateMemberPaymentPresenter = overrides?.updateMemberPaymentPresenter ?? { viewModel: { success: true } };

    const getPrizesPresenter = overrides?.getPrizesPresenter ?? { viewModel: { prizes: [] } };
    const createPrizePresenter = overrides?.createPrizePresenter ?? { viewModel: { success: true } };
    const awardPrizePresenter = overrides?.awardPrizePresenter ?? { viewModel: { success: true } };
    const deletePrizePresenter = overrides?.deletePrizePresenter ?? { viewModel: { success: true } };

    const getWalletPresenter = overrides?.getWalletPresenter ?? { viewModel: { balance: 0 } };
    const processWalletTransactionPresenter =
      overrides?.processWalletTransactionPresenter ?? { viewModel: { success: true } };

    const service = new PaymentsService(
      getPaymentsUseCase as any,
      createPaymentUseCase as any,
      updatePaymentStatusUseCase as any,
      getMembershipFeesUseCase as any,
      upsertMembershipFeeUseCase as any,
      updateMemberPaymentUseCase as any,
      getPrizesUseCase as any,
      createPrizeUseCase as any,
      awardPrizeUseCase as any,
      deletePrizeUseCase as any,
      getWalletUseCase as any,
      processWalletTransactionUseCase as any,
      logger as any,
      getPaymentsPresenter as any,
      createPaymentPresenter as any,
      updatePaymentStatusPresenter as any,
      getMembershipFeesPresenter as any,
      upsertMembershipFeePresenter as any,
      updateMemberPaymentPresenter as any,
      getPrizesPresenter as any,
      createPrizePresenter as any,
      awardPrizePresenter as any,
      deletePrizePresenter as any,
      getWalletPresenter as any,
      processWalletTransactionPresenter as any,
    );

    return {
      service,
      getPaymentsUseCase,
      createPaymentUseCase,
      updatePaymentStatusUseCase,
      getMembershipFeesUseCase,
      upsertMembershipFeeUseCase,
      updateMemberPaymentUseCase,
      getPrizesUseCase,
      createPrizeUseCase,
      awardPrizeUseCase,
      deletePrizeUseCase,
      getWalletUseCase,
      processWalletTransactionUseCase,
      getPaymentsPresenter,
      createPaymentPresenter,
      updatePaymentStatusPresenter,
      getMembershipFeesPresenter,
      upsertMembershipFeePresenter,
      updateMemberPaymentPresenter,
      getPrizesPresenter,
      createPrizePresenter,
      awardPrizePresenter,
      deletePrizePresenter,
      getWalletPresenter,
      processWalletTransactionPresenter,
    };
  }

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

  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 any)).rejects.toThrow('REPOSITORY_ERROR');
  });

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

  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 any)).rejects.toThrow('REPOSITORY_ERROR');
  });

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

  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 any)).rejects.toThrow('REPOSITORY_ERROR');
  });

  it('getMembershipFees returns viewModel on success', async () => {
    const { service, getMembershipFeesUseCase, getMembershipFeesPresenter } = makeService({
      getMembershipFeesPresenter: { viewModel: { fee: { amount: 1 }, payments: [] } },
    });

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

  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 any)).rejects.toThrow('REPOSITORY_ERROR');
  });

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

    await expect(service.upsertMembershipFee({ leagueId: 'l1' } as any)).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 any)) },
    });

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

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

    await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).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 any)).rejects.toThrow('REPOSITORY_ERROR');
  });

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

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

    await expect(service.getPrizes({ leagueId: 'l1', seasonId: 's1' } as any)).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(undefined)) };
    const { service } = makeService({
      createPrizeUseCase,
      createPrizePresenter: { viewModel: { success: true } },
    });

    await expect(service.createPrize({ leagueId: 'l1' } as any)).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(undefined)) };
    const { service } = makeService({
      awardPrizeUseCase,
      awardPrizePresenter: { viewModel: { success: true } },
    });

    await expect(service.awardPrize({ prizeId: 'p1' } as any)).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(undefined)) };
    const { service } = makeService({
      deletePrizeUseCase,
      deletePrizePresenter: { viewModel: { success: true } },
    });

    await expect(service.deletePrize({ prizeId: 'p1' } as any)).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(undefined)) };
    const { service } = makeService({
      getWalletUseCase,
      getWalletPresenter: { viewModel: { balance: 10 } },
    });

    await expect(service.getWallet({ leagueId: 'l1' } as any)).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(undefined)) };
    const { service } = makeService({
      processWalletTransactionUseCase,
      processWalletTransactionPresenter: { viewModel: { success: true } },
    });

    await expect(service.processWalletTransaction({ leagueId: 'l1' } as any)).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 any)) },
    });

    await expect(service.getPayments({ leagueId: 'l1' } as any)).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 any)) },
    });

    await expect(service.createPayment({ leagueId: 'l1' } as any)).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 any)) },
    });

    await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).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 any)) },
    });

    await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as any)).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 any)) },
    });

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