import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import {
  GetUserFeedUseCase,
  type GetUserFeedApplicationError,
  type GetUserFeedInput,
  type GetUserFeedResult,
} from './GetUserFeedUseCase';
import type { IFeedRepository } from '../../domain/repositories/IFeedRepository';
import type { FeedItem } from '../../domain/types/FeedItem';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';

describe('GetUserFeedUseCase', () => {
  let feedRepository: IFeedRepository & { getFeedForDriver: Mock };
  let logger: Logger & { debug: Mock; info: Mock; warn: Mock; error: Mock };
  let output: UseCaseOutputPort<GetUserFeedResult> & { present: Mock };
  let useCase: GetUserFeedUseCase;

  beforeEach(() => {
    feedRepository = {
      getFeedForDriver: vi.fn(),
      getGlobalFeed: vi.fn(),
    } as unknown as IFeedRepository & { getFeedForDriver: Mock };

    logger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    } as unknown as Logger & { debug: Mock; info: Mock; warn: Mock; error: Mock };

    output = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<GetUserFeedResult> & { present: Mock };

    useCase = new GetUserFeedUseCase(feedRepository, logger, output);
  });

  it('presents feed items when repository returns items', async () => {
    const items: FeedItem[] = [
      {
        id: 'item-1',
        timestamp: new Date('2025-01-01T00:00:00.000Z'),
        type: 'friend-joined-league',
        actorFriendId: 'friend-1',
        leagueId: 'league-1',
        headline: 'Friend joined a league',
        body: 'Friend joined League 1',
        ctaLabel: 'View league',
        ctaHref: '/leagues/league-1',
      },
      {
        id: 'item-2',
        timestamp: new Date('2025-01-02T00:00:00.000Z'),
        type: 'friend-finished-race',
        actorDriverId: 'driver-2',
        raceId: 'race-1',
        position: 3,
        headline: 'Race finished',
      },
    ];

    feedRepository.getFeedForDriver.mockResolvedValue(items);

    const input: GetUserFeedInput = { driverId: 'driver-1', limit: 10 };
    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();

    expect(feedRepository.getFeedForDriver).toHaveBeenCalledTimes(1);
    expect(feedRepository.getFeedForDriver).toHaveBeenCalledWith('driver-1', 10);

    expect(output.present).toHaveBeenCalledTimes(1);
    const presented = (output.present as Mock).mock.calls[0]![0] as GetUserFeedResult;
    expect(presented.items).toEqual(items);

    expect(logger.warn).not.toHaveBeenCalled();
  });

  it('warns and presents empty list when no items exist', async () => {
    feedRepository.getFeedForDriver.mockResolvedValue([]);

    const input: GetUserFeedInput = { driverId: 'driver-1' };
    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(output.present).toHaveBeenCalledTimes(1);

    const presented = (output.present as Mock).mock.calls[0]![0] as GetUserFeedResult;
    expect(presented.items).toEqual([]);

    expect(logger.warn).toHaveBeenCalledTimes(1);
    expect((logger.warn as Mock).mock.calls[0]![0]).toBe(
      'No feed items found for driverId: driver-1',
    );
  });

  it('returns REPOSITORY_ERROR when repository throws', async () => {
    feedRepository.getFeedForDriver.mockRejectedValue(new Error('DB error'));

    const input: GetUserFeedInput = { driverId: 'driver-1', limit: 5 };
    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);

    const err = result.unwrapErr() as GetUserFeedApplicationError;
    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details.message).toBe('DB error');

    expect(output.present).not.toHaveBeenCalled();
    expect(logger.error).toHaveBeenCalledTimes(1);
  });
});