import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { SponsorshipRequestRepository } from '../../domain/repositories/SponsorshipRequestRepository';
import {
    RejectSponsorshipRequestUseCase,
    type RejectSponsorshipRequestErrorCode,
    type RejectSponsorshipRequestInput,
    type RejectSponsorshipRequestResult,
} from './RejectSponsorshipRequestUseCase';

describe('RejectSponsorshipRequestUseCase', () => {
  let useCase: RejectSponsorshipRequestUseCase;
  let sponsorshipRequestRepo: { findById: Mock; update: Mock };
  let logger: Logger;

  beforeEach(() => {
    sponsorshipRequestRepo = { findById: vi.fn(), update: vi.fn() };
    logger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    };

    useCase = new RejectSponsorshipRequestUseCase(
      sponsorshipRequestRepo as unknown as SponsorshipRequestRepository,
      logger,
    );
  });

  const unwrapError = (
    result: Result<RejectSponsorshipRequestResult, ApplicationErrorCode<RejectSponsorshipRequestErrorCode, { message: string }>>,
  ): ApplicationErrorCode<RejectSponsorshipRequestErrorCode, { message: string }> => result.unwrapErr();

  it('should return not found error when request does not exist', async () => {
    sponsorshipRequestRepo.findById.mockResolvedValue(null);

    const input: RejectSponsorshipRequestInput = {
      requestId: 'request-1',
      respondedBy: 'driver-1',
      reason: 'Not interested',
    };

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const error = unwrapError(result);
    expect(error.code).toBe('SPONSORSHIP_REQUEST_NOT_FOUND');
    expect(error.details?.message).toBe('Sponsorship request not found');
    expect(sponsorshipRequestRepo.update).not.toHaveBeenCalled();
  });

  it('should return not pending error when request is not pending', async () => {
    const mockRequest = {
      id: 'request-1',
      status: 'accepted',
      isPending: vi.fn().mockReturnValue(false),
    };
    sponsorshipRequestRepo.findById.mockResolvedValue(mockRequest);

    const input: RejectSponsorshipRequestInput = {
      requestId: 'request-1',
      respondedBy: 'driver-1',
      reason: 'Not interested',
    };

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const error = unwrapError(result);
    expect(error.code).toBe('SPONSORSHIP_REQUEST_NOT_PENDING');
    expect(error.details?.message).toBe('Sponsorship request is not pending');
    expect(sponsorshipRequestRepo.update).not.toHaveBeenCalled();
  });

  it('should reject the request successfully with reason', async () => {
    const respondedAt = new Date('2023-01-01T00:00:00Z');
    const mockRequest = {
      id: 'request-1',
      status: 'pending',
      isPending: vi.fn().mockReturnValue(true),
      reject: vi.fn().mockReturnValue({
        id: 'request-1',
        respondedAt,
        rejectionReason: 'Not interested',
      }),
    };
    sponsorshipRequestRepo.findById.mockResolvedValue(mockRequest);
    sponsorshipRequestRepo.update.mockResolvedValue(undefined);

    const input: RejectSponsorshipRequestInput = {
      requestId: 'request-1',
      respondedBy: 'driver-1',
      reason: 'Not interested',
    };

    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    const successResult = result.unwrap();
    expect(successResult.requestId).toBe('request-1');
    expect(successResult.status).toBe('rejected');
    expect(successResult.respondedAt).toBe(respondedAt);
    expect(successResult.rejectionReason).toBe('Not interested');
    
    expect(sponsorshipRequestRepo.update).toHaveBeenCalledTimes(1);
    expect(mockRequest.reject).toHaveBeenCalledWith('driver-1', 'Not interested');
  });

  it('should reject the request successfully without reason', async () => {
    const respondedAt = new Date('2023-01-01T00:00:00Z');
    const mockRequest = {
      id: 'request-1',
      status: 'pending',
      isPending: vi.fn().mockReturnValue(true),
      reject: vi.fn().mockReturnValue({
        id: 'request-1',
        respondedAt,
        rejectionReason: undefined,
      }),
    };
    sponsorshipRequestRepo.findById.mockResolvedValue(mockRequest);
    sponsorshipRequestRepo.update.mockResolvedValue(undefined);

    const input: RejectSponsorshipRequestInput = {
      requestId: 'request-1',
      respondedBy: 'driver-1',
    };

    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    const successResult = result.unwrap();
    expect(successResult.requestId).toBe('request-1');
    expect(successResult.status).toBe('rejected');
    expect(successResult.respondedAt).toBe(respondedAt);
    expect(successResult.rejectionReason).toBeUndefined();
    
    expect(sponsorshipRequestRepo.update).toHaveBeenCalledTimes(1);
    expect(mockRequest.reject).toHaveBeenCalledWith('driver-1', undefined);
  });

  it('should wrap repository errors in REPOSITORY_ERROR', async () => {
    const error = new Error('DB failure');
    sponsorshipRequestRepo.findById.mockRejectedValue(error);

    const input: RejectSponsorshipRequestInput = {
      requestId: 'request-1',
      respondedBy: 'driver-1',
    };

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const appError = unwrapError(result);
    expect(appError.code).toBe('REPOSITORY_ERROR');
    expect(appError.details?.message).toBe('DB failure');
  });
});
