import { MusicWorldService } from './music-world.service';

describe('MusicWorldService', () => {
  const createService = () => {
    const feedService = {
      getExplore: jest.fn(),
    };
    const searchService = {
      searchPosts: jest.fn(),
    };

    return {
      service: new MusicWorldService(feedService as any, searchService as any),
      feedService,
      searchService,
    };
  };

  it('returns the complete music-world navigation model', () => {
    const { service } = createService();

    const result = service.getMusicWorld();

    expect(result.title).toBeTruthy();
    expect(result.searchPlaceholder).toBeTruthy();
    expect(result.cards).toHaveLength(6);
    expect(result.cards.filter((card) => card.type === 'search')).toHaveLength(4);
    expect(result.cards.filter((card) => card.type === 'navigation')).toHaveLength(2);
    expect(result.cards.map((card) => card.key)).toEqual([
      'oud',
      'ney',
      'hijaz',
      'levantine',
      'artists',
      'explore',
    ]);
    expect(result.sections.map((section) => section.endpoint)).toEqual([
      '/api/v1/feed/trending',
      '/api/v1/feed/explore',
      '/api/v1/users/discover',
    ]);
  });

  it('filters non-post feed entries and normalizes sparse post shapes', async () => {
    const { service, feedService } = createService();
    feedService.getExplore.mockResolvedValue({
      items: [
        null,
        'invalid',
        { feedItemType: 'featured_marketplace' },
        {
          _id: 'post-1',
          feedItemType: 'post',
          content: 'music',
          media: { mediaType: 'image' },
          author: {
            _id: 'user-1',
            name: 'Artist',
            username: 'artist',
            avatar: '/avatar.jpg',
            isVerified: 1,
          },
          imageVariants: [{ mediumUrl: '/medium.jpg' }],
          liked: true,
          savedByMe: true,
          createdAt: '2026-01-01T00:00:00.000Z',
        },
        {
          id: 'post-2',
          feedItemType: 'post',
          postType: 'video',
          displayUrl: '/display.jpg',
          thumbnailUrl: '/thumbnail.jpg',
          authorId: null,
          engagement: {
            likesCount: 4,
            commentsCount: 3,
            viewCount: 12,
            playCount: 8,
          },
          likedByMe: false,
          saved: false,
        },
      ],
      page: 2,
      limit: 2,
      total: 5,
      totalPages: 3,
      nextCursor: 'cursor-2',
      pagination: { hasNextPage: true },
    });

    const result = await service.getExplore('viewer-1', { page: 2, limit: 2 } as any);

    expect(feedService.getExplore).toHaveBeenCalledWith('viewer-1', { page: 2, limit: 2 });
    expect(result).toMatchObject({
      count: 2,
      page: 2,
      limit: 2,
      total: 5,
      totalPages: 3,
      nextCursor: 'cursor-2',
      pagination: { hasNextPage: true },
    });
    expect(result.items[0]).toEqual({
      id: 'post-1',
      postType: 'image',
      content: 'music',
      thumbnailUrl: '/medium.jpg',
      displayUrl: '/medium.jpg',
      media: { mediaType: 'image' },
      author: {
        id: 'user-1',
        name: 'Artist',
        username: 'artist',
        avatar: '/avatar.jpg',
        isVerified: true,
      },
      engagement: {
        likesCount: 0,
        commentsCount: 0,
        viewCount: 0,
        playCount: 0,
      },
      isLiked: true,
      isSaved: true,
      createdAt: '2026-01-01T00:00:00.000Z',
    });
    expect(result.items[1]).toMatchObject({
      id: 'post-2',
      postType: 'video',
      thumbnailUrl: '/thumbnail.jpg',
      displayUrl: '/display.jpg',
      author: { id: '', isVerified: false },
      engagement: { likesCount: 4, commentsCount: 3, viewCount: 12, playCount: 8 },
      isLiked: false,
      isSaved: false,
      createdAt: null,
    });
  });

  it('defensively excludes private authors from explore and search results', async () => {
    const { service, feedService, searchService } = createService();
    const privateItem = {
      id: 'private-post',
      feedItemType: 'post',
      authorId: { id: 'private-user', isPrivate: true },
    };
    feedService.getExplore.mockResolvedValue({ items: [privateItem], total: 1 });
    searchService.searchPosts.mockResolvedValue({ items: [privateItem], total: 1 });

    await expect(service.getExplore('viewer', {} as any)).resolves.toMatchObject({
      items: [],
      count: 0,
    });
    await expect(service.search('viewer', { q: 'oud' } as any)).resolves.toMatchObject({
      items: [],
    });
  });

  it('returns an empty page when feed items are not an array', async () => {
    const { service, feedService } = createService();
    feedService.getExplore.mockResolvedValue({
      items: null,
      page: 1,
      limit: 20,
      total: 0,
      totalPages: 1,
    });

    await expect(service.getExplore('viewer-1', {} as any)).resolves.toEqual({
      items: [],
      count: 0,
      page: 1,
      limit: 20,
      total: 0,
      totalPages: 1,
      nextCursor: null,
      pagination: undefined,
    });
  });

  it('forces post search and resolves every supported image fallback', async () => {
    const { service, searchService } = createService();
    searchService.searchPosts.mockResolvedValue({
      items: [
        {
          id: 'high',
          imageVariants: [{ mediumUrl: ' ', highUrl: '/high.jpg' }],
        },
        {
          id: 'low',
          imageVariants: [{ lowUrl: '/low.jpg' }],
        },
        {
          id: 'original',
          imageVariants: [{ originalUrl: '/original.jpg' }],
        },
        {
          id: 'image-item',
          imageItems: [{ url: '/item.jpg' }],
        },
        {
          id: 'image-url',
          imageUrls: ['/url.jpg'],
        },
        {
          id: 'media-thumbnail',
          media: { thumbnailUrl: '/media-thumb.jpg' },
        },
      ],
      total: 6,
    });

    const result = await service.search('viewer-2', { q: 'oud', type: 'users' } as any);

    expect(searchService.searchPosts).toHaveBeenCalledWith('viewer-2', {
      q: 'oud',
      type: 'posts',
    });
    expect(result.items.map((item: { displayUrl: string }) => item.displayUrl)).toEqual([
      '/high.jpg',
      '/low.jpg',
      '/original.jpg',
      '/item.jpg',
      '/url.jpg',
      '/media-thumb.jpg',
    ]);
    expect(result.items.map((item: { thumbnailUrl: string }) => item.thumbnailUrl)).toEqual([
      '/high.jpg',
      '/low.jpg',
      '/original.jpg',
      '/item.jpg',
      '/url.jpg',
      '/media-thumb.jpg',
    ]);
  });

  it('propagates feed and search failures', async () => {
    const { service, feedService, searchService } = createService();
    feedService.getExplore.mockRejectedValue(new Error('feed unavailable'));
    searchService.searchPosts.mockRejectedValue(new Error('search unavailable'));

    await expect(service.getExplore('viewer', {} as any)).rejects.toThrow('feed unavailable');
    await expect(service.search('viewer', { q: 'oud' } as any)).rejects.toThrow(
      'search unavailable',
    );
  });
});
