import { SponsorshipTier } from '@core/racing/domain/entities/season/SeasonSponsorship';
import { SponsorableEntityType, SponsorshipRequest, SponsorshipRequestStatus } from '@core/racing/domain/entities/SponsorshipRequest';
import { Money } from '@core/racing/domain/value-objects/Money';
import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { InMemorySponsorshipRequestRepository } from './InMemorySponsorshipRequestRepository';

describe('InMemorySponsorshipRequestRepository', () => {
  let repository: InMemorySponsorshipRequestRepository;
  let mockLogger: Logger;

  beforeEach(() => {
    mockLogger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    };
    repository = new InMemorySponsorshipRequestRepository(mockLogger);
  });

  const createTestRequest = (
    id: string,
    sponsorId: string = 'sponsor-1',
    entityType: SponsorableEntityType = 'team',
    entityId: string = 'entity-1',
    status: SponsorshipRequestStatus = 'pending',
    tier: SponsorshipTier = 'main',
    offeredAmount: Money = Money.create(1000, 'USD')
  ): SponsorshipRequest => {
    return SponsorshipRequest.create({
      id,
      sponsorId,
      entityType,
      entityId,
      tier,
      offeredAmount,
      status,
      createdAt: new Date(),
    });
  };

  describe('constructor', () => {
    it('should initialize with a logger', () => {
      expect(repository).toBeDefined();
      expect(mockLogger.info).toHaveBeenCalledWith('InMemorySponsorshipRequestRepository initialized.');
    });

    it('should seed initial requests', () => {
      const request = createTestRequest('req-1');
      new InMemorySponsorshipRequestRepository(mockLogger, [request]);
      expect(mockLogger.debug).toHaveBeenCalledWith('Seeded sponsorship request: req-1.');
    });
  });

  describe('findById', () => {
    it('should return the request if found', async () => {
      const request = createTestRequest('req-1');
      await repository.create(request);
      const result = await repository.findById('req-1');
      expect(result).toEqual(request);
      expect(mockLogger.info).toHaveBeenCalledWith('Found request by ID: req-1.');
    });

    it('should return null if not found', async () => {
      const result = await repository.findById('nonexistent');
      expect(result).toBeNull();
      expect(mockLogger.warn).toHaveBeenCalledWith('Request with ID nonexistent not found.');
    });
  });

  describe('findByEntity', () => {
    it('should return requests for the entity', async () => {
      const request1 = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1');
      const request2 = createTestRequest('req-2', 'sponsor-2', 'team', 'entity-1');
      const request3 = createTestRequest('req-3', 'sponsor-1', 'driver', 'entity-2');
      await repository.create(request1);
      await repository.create(request2);
      await repository.create(request3);
      const result = await repository.findByEntity('team', 'entity-1');
      expect(result).toHaveLength(2);
      expect(result).toContain(request1);
      expect(result).toContain(request2);
      expect(mockLogger.info).toHaveBeenCalledWith('Found 2 requests for entity team:entity-1.');
    });

    it('should return empty array if no requests', async () => {
      const result = await repository.findByEntity('team', 'nonexistent');
      expect(result).toEqual([]);
      expect(mockLogger.info).toHaveBeenCalledWith('Found 0 requests for entity team:nonexistent.');
    });
  });

  describe('findPendingByEntity', () => {
    it('should return pending requests for the entity', async () => {
      const pending = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1', 'pending');
      const accepted = createTestRequest('req-2', 'sponsor-1', 'team', 'entity-1', 'accepted');
      await repository.create(pending);
      await repository.create(accepted);
      const result = await repository.findPendingByEntity('team', 'entity-1');
      expect(result).toHaveLength(1);
      expect(result[0]).toEqual(pending);
      expect(mockLogger.info).toHaveBeenCalledWith('Found 1 pending requests for entity team:entity-1.');
    });
  });

  describe('findBySponsorId', () => {
    it('should return requests by sponsor', async () => {
      const request1 = createTestRequest('req-1', 'sponsor-1');
      const request2 = createTestRequest('req-2', 'sponsor-1');
      const request3 = createTestRequest('req-3', 'sponsor-2');
      await repository.create(request1);
      await repository.create(request2);
      await repository.create(request3);
      const result = await repository.findBySponsorId('sponsor-1');
      expect(result).toHaveLength(2);
      expect(result).toContain(request1);
      expect(result).toContain(request2);
      expect(mockLogger.info).toHaveBeenCalledWith('Found 2 requests by sponsor ID: sponsor-1.');
    });
  });

  describe('findByStatus', () => {
    it('should return requests by status', async () => {
      const pending = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1', 'pending');
      const accepted = createTestRequest('req-2', 'sponsor-1', 'team', 'entity-1', 'accepted');
      await repository.create(pending);
      await repository.create(accepted);
      const result = await repository.findByStatus('pending');
      expect(result).toHaveLength(1);
      expect(result[0]).toEqual(pending);
      expect(mockLogger.info).toHaveBeenCalledWith('Found 1 requests with status: pending.');
    });
  });

  describe('findBySponsorIdAndStatus', () => {
    it('should return requests by sponsor and status', async () => {
      const pending1 = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1', 'pending');
      const accepted1 = createTestRequest('req-2', 'sponsor-1', 'team', 'entity-1', 'accepted');
      const pending2 = createTestRequest('req-3', 'sponsor-2', 'team', 'entity-1', 'pending');
      await repository.create(pending1);
      await repository.create(accepted1);
      await repository.create(pending2);
      const result = await repository.findBySponsorIdAndStatus('sponsor-1', 'pending');
      expect(result).toHaveLength(1);
      expect(result[0]).toEqual(pending1);
      expect(mockLogger.info).toHaveBeenCalledWith('Found 1 requests by sponsor ID sponsor-1 and status pending.');
    });
  });

  describe('hasPendingRequest', () => {
    it('should return true if pending request exists', async () => {
      const request = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1', 'pending');
      await repository.create(request);
      const result = await repository.hasPendingRequest('sponsor-1', 'team', 'entity-1');
      expect(result).toBe(true);
      expect(mockLogger.info).toHaveBeenCalledWith('Pending request for sponsor sponsor-1, entity team:entity-1 exists: true.');
    });

    it('should return false if no pending request', async () => {
      const result = await repository.hasPendingRequest('sponsor-1', 'team', 'entity-1');
      expect(result).toBe(false);
      expect(mockLogger.info).toHaveBeenCalledWith('Pending request for sponsor sponsor-1, entity team:entity-1 exists: false.');
    });
  });

  describe('countPendingByEntity', () => {
    it('should count pending requests for entity', async () => {
      const request1 = createTestRequest('req-1', 'sponsor-1', 'team', 'entity-1', 'pending');
      const request2 = createTestRequest('req-2', 'sponsor-2', 'team', 'entity-1', 'pending');
      const accepted = createTestRequest('req-3', 'sponsor-3', 'team', 'entity-1', 'accepted');
      await repository.create(request1);
      await repository.create(request2);
      await repository.create(accepted);
      const result = await repository.countPendingByEntity('team', 'entity-1');
      expect(result).toBe(2);
      expect(mockLogger.info).toHaveBeenCalledWith('Count of pending requests for entity team:entity-1: 2.');
    });
  });

  describe('create', () => {
    it('should create a new request', async () => {
      const request = createTestRequest('req-1');
      const result = await repository.create(request);
      expect(result).toEqual(request);
      expect(mockLogger.info).toHaveBeenCalledWith('Sponsorship request req-1 created successfully.');
    });

    it('should throw if request already exists', async () => {
      const request = createTestRequest('req-1');
      await repository.create(request);
      await expect(repository.create(request)).rejects.toThrow('Sponsorship request already exists');
      expect(mockLogger.warn).toHaveBeenCalledWith('Request with ID req-1 already exists.');
    });
  });

  describe('update', () => {
    it('should update existing request', async () => {
      const request = createTestRequest('req-1');
      await repository.create(request);
      const updated = request.accept('responder-1');
      const result = await repository.update(updated);
      expect(result).toEqual(updated);
      expect(mockLogger.info).toHaveBeenCalledWith('Sponsorship request req-1 updated successfully.');
    });

    it('should throw if request not found', async () => {
      const request = createTestRequest('req-1');
      await expect(repository.update(request)).rejects.toThrow('Sponsorship request not found');
      expect(mockLogger.warn).toHaveBeenCalledWith('Request with ID req-1 not found for update.');
    });
  });

  describe('delete', () => {
    it('should delete existing request', async () => {
      const request = createTestRequest('req-1');
      await repository.create(request);
      await repository.delete('req-1');
      expect(mockLogger.info).toHaveBeenCalledWith('Sponsorship request req-1 deleted successfully.');
      const found = await repository.findById('req-1');
      expect(found).toBeNull();
    });

    it('should not throw if request not found', async () => {
      await repository.delete('nonexistent');
      expect(mockLogger.warn).toHaveBeenCalledWith('Request with ID nonexistent not found for deletion.');
    });
  });

  describe('exists', () => {
    it('should return true if exists', async () => {
      const request = createTestRequest('req-1');
      await repository.create(request);
      const result = await repository.exists('req-1');
      expect(result).toBe(true);
    });

    it('should return false if not exists', async () => {
      const result = await repository.exists('nonexistent');
      expect(result).toBe(false);
    });
  });
});