/**
 * Tests for PlaylistFollowButton Component
 * FE-TEST-007: Test playlist follow button component
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { PlaylistFollowButton } from './PlaylistFollowButton';
import {
  followPlaylist,
  unfollowPlaylist,
  getPlaylist,
  getPlaylistFollowStatus,
} from '../services/playlistService';
import { useAuthStore } from '@/stores/auth';
import { useToast } from '@/hooks/useToast';

// Mock dependencies
vi.mock('../services/playlistService', () => ({
  followPlaylist: vi.fn(),
  unfollowPlaylist: vi.fn(),
  getPlaylist: vi.fn(),
  getPlaylistFollowStatus: vi.fn(),
}));

vi.mock('@/stores/auth', () => ({
  useAuthStore: vi.fn(),
}));

vi.mock('@/hooks/useToast', () => ({
  useToast: vi.fn(),
}));

const createTestQueryClient = () =>
  new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

const TestWrapper = ({ children }: { children: React.ReactNode }) => {
  const queryClient = createTestQueryClient();
  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
};

describe('PlaylistFollowButton', () => {
  const mockUser = {
    id: '2',
    username: 'testuser',
    email: 'test@example.com',
  };

  const mockShowSuccess = vi.fn();
  const mockShowError = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(useAuthStore).mockReturnValue({
      user: mockUser,
    } as any);
    vi.mocked(useToast).mockReturnValue({
      success: mockShowSuccess,
      error: mockShowError,
    } as any);
    vi.mocked(getPlaylist).mockResolvedValue({
      id: '1',
      user_id: '1', // Different from mockUser.id
    } as any);
    vi.mocked(getPlaylistFollowStatus).mockResolvedValue({
      is_following: false,
      follower_count: 10,
    });
  });

  it('should render follow button when not following', async () => {
    render(
      <TestWrapper>
        <PlaylistFollowButton playlistId="1" initialFollowing={false} />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('Suivre')).toBeInTheDocument();
    });
  });

  it('should render unfollow button when following', async () => {
    vi.mocked(getPlaylistFollowStatus).mockResolvedValue({
      is_following: true,
      follower_count: 11,
    });

    render(
      <TestWrapper>
        <PlaylistFollowButton playlistId="1" initialFollowing={true} />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('Abonné')).toBeInTheDocument();
    });
  });

  it('should not render for playlist owner', async () => {
    vi.mocked(getPlaylist).mockResolvedValue({
      id: '1',
      user_id: '2', // Same as mockUser.id
    } as any);
    vi.mocked(getPlaylistFollowStatus).mockResolvedValue({
      is_following: false,
      follower_count: 0,
    });

    const { container } = render(
      <TestWrapper>
        <PlaylistFollowButton playlistId="1" />
      </TestWrapper>,
    );

    await waitFor(() => {
      // Button should not be rendered for owner
      // Component returns null, so no buttons should be found
      const buttons = screen.queryAllByRole('button');
      expect(buttons.length).toBe(0);
    }, { timeout: 3000 });
  });

  it('should not render when user is not logged in', async () => {
    vi.mocked(useAuthStore).mockReturnValue({
      user: null,
    } as any);
    vi.mocked(getPlaylist).mockResolvedValue({
      id: '1',
      user_id: '1',
    } as any);

    const { container } = render(
      <TestWrapper>
        <PlaylistFollowButton playlistId="1" />
      </TestWrapper>,
    );

    await waitFor(() => {
      // Component should return null when user is not logged in
      const buttons = screen.queryAllByRole('button');
      expect(buttons.length).toBe(0);
    });
  });

  it('should call followPlaylist when follow button is clicked', async () => {
    const user = userEvent.setup();
    vi.mocked(followPlaylist).mockResolvedValue({});

    render(
      <TestWrapper>
        <PlaylistFollowButton playlistId="1" initialFollowing={false} />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('Suivre')).toBeInTheDocument();
    });

    const followButton = screen.getByText('Suivre');
    await user.click(followButton);

    await waitFor(() => {
      expect(followPlaylist).toHaveBeenCalledWith('1');
    });
  });

  it('should call unfollowPlaylist when unfollow button is clicked', async () => {
    const user = userEvent.setup();
    vi.mocked(unfollowPlaylist).mockResolvedValue({});
    vi.mocked(getPlaylistFollowStatus).mockResolvedValue({
      is_following: true,
      follower_count: 11,
    });

    render(
      <TestWrapper>
        <PlaylistFollowButton playlistId="1" initialFollowing={true} />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('Abonné')).toBeInTheDocument();
    });

    const unfollowButton = screen.getByText('Abonné');
    await user.click(unfollowButton);

    await waitFor(() => {
      expect(unfollowPlaylist).toHaveBeenCalledWith('1');
    });
  });

  it('should show success message on follow', async () => {
    const user = userEvent.setup();
    vi.mocked(followPlaylist).mockResolvedValue({});

    render(
      <TestWrapper>
        <PlaylistFollowButton playlistId="1" initialFollowing={false} />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('Suivre')).toBeInTheDocument();
    });

    const followButton = screen.getByText('Suivre');
    await user.click(followButton);

    await waitFor(() => {
      expect(mockShowSuccess).toHaveBeenCalledWith(
        'Vous suivez maintenant cette playlist',
      );
    });
  });

  it('should show success message on unfollow', async () => {
    const user = userEvent.setup();
    vi.mocked(unfollowPlaylist).mockResolvedValue({});
    vi.mocked(getPlaylistFollowStatus).mockResolvedValue({
      is_following: true,
      follower_count: 11,
    });

    render(
      <TestWrapper>
        <PlaylistFollowButton playlistId="1" initialFollowing={true} />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('Abonné')).toBeInTheDocument();
    });

    const unfollowButton = screen.getByText('Abonné');
    await user.click(unfollowButton);

    await waitFor(() => {
      expect(mockShowSuccess).toHaveBeenCalledWith(
        'Vous ne suivez plus cette playlist',
      );
    });
  });

  it('should show error message on follow failure', async () => {
    const user = userEvent.setup();
    vi.mocked(followPlaylist).mockRejectedValue(new Error('Follow failed'));

    render(
      <TestWrapper>
        <PlaylistFollowButton playlistId="1" initialFollowing={false} />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('Suivre')).toBeInTheDocument();
    });

    const followButton = screen.getByText('Suivre');
    await user.click(followButton);

    await waitFor(() => {
      expect(mockShowError).toHaveBeenCalled();
    });
  });

  it('should show follower count when showCount is true', async () => {
    render(
      <TestWrapper>
        <PlaylistFollowButton
          playlistId="1"
          initialFollowing={false}
          initialFollowerCount={25}
          showCount={true}
        />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('(25)')).toBeInTheDocument();
    });
  });

  it('should update follower count optimistically', async () => {
    const user = userEvent.setup();
    vi.mocked(followPlaylist).mockResolvedValue({});

    render(
      <TestWrapper>
        <PlaylistFollowButton
          playlistId="1"
          initialFollowing={false}
          initialFollowerCount={10}
          showCount={true}
        />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('Suivre')).toBeInTheDocument();
    });

    const followButton = screen.getByText('Suivre');
    await user.click(followButton);

    // Follower count should increase optimistically
    await waitFor(() => {
      expect(screen.getByText('(11)')).toBeInTheDocument();
    });
  });

  it('should call onFollowChange callback', async () => {
    const user = userEvent.setup();
    const mockOnFollowChange = vi.fn();
    vi.mocked(followPlaylist).mockResolvedValue({});

    render(
      <TestWrapper>
        <PlaylistFollowButton
          playlistId="1"
          initialFollowing={false}
          onFollowChange={mockOnFollowChange}
        />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('Suivre')).toBeInTheDocument();
    });

    const followButton = screen.getByText('Suivre');
    await user.click(followButton);

    await waitFor(() => {
      expect(mockOnFollowChange).toHaveBeenCalledWith(true);
    });
  });

  it('should be disabled during update', async () => {
    const user = userEvent.setup();
    vi.mocked(followPlaylist).mockImplementation(
      () => new Promise((resolve) => setTimeout(resolve, 100)),
    );

    render(
      <TestWrapper>
        <PlaylistFollowButton playlistId="1" initialFollowing={false} />
      </TestWrapper>,
    );

    await waitFor(() => {
      expect(screen.getByText('Suivre')).toBeInTheDocument();
    });

    const followButton = screen.getByText('Suivre');
    await user.click(followButton);

    // Button should show loading state
    await waitFor(() => {
      expect(screen.getByText(/abonnement/i)).toBeInTheDocument();
      expect(followButton).toBeDisabled();
    });
  });
});

