/**
 * Tests d'intégration pour la collaboration des playlists
 * T0487: Create Playlist Collaboration Integration Tests Frontend
 */

import { describe, it, expect, vi, beforeEach, afterEach } 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 { MemoryRouter, Routes, Route } from 'react-router-dom';
import { PlaylistDetailPage } from '../pages/PlaylistDetailPage';
import * as playlistService from '../services/playlistService';
import { apiClient } from '@/services/api/client';
import type { Playlist } from '../types';
import type { PlaylistCollaborator } from '../services/playlistService';

// Mock ResizeObserver
global.ResizeObserver = vi.fn().mockImplementation(() => ({
  observe: vi.fn(),
  unobserve: vi.fn(),
  disconnect: vi.fn(),
}));

// Mock window.confirm
global.confirm = vi.fn(() => true);

// Mock playlistService
vi.mock('../services/playlistService', () => ({
  getPlaylist: vi.fn(),
  getCollaborators: vi.fn(),
  addCollaborator: vi.fn(),
  removeCollaborator: vi.fn(),
  updateCollaboratorPermission: vi.fn(),
}));

// Mock apiClient
vi.mock('@/services/api/client', () => ({
  apiClient: {
    get: vi.fn(),
  },
}));

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

// Mock useAuthStore
vi.mock('@/features/auth/store/authStore', () => ({
  useAuthStore: (selector: any) => {
    const state = {
      user: { id: 1, username: 'owner' },
      isAuthenticated: true,
      isLoading: false,
    };
    return selector ? selector(state) : state;
  },
}));

// Mock usePlayerStore
vi.mock('@/features/player/store/playerStore', () => ({
  usePlayerStore: () => ({
    play: vi.fn(),
    currentTrack: null,
    isPlaying: false,
  }),
}));

// Mock useDebounce
vi.mock('@/hooks/useDebounce', () => ({
  useDebounce: (value: string) => value,
}));

// Helper pour créer un QueryClient pour chaque test
function createQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
        cacheTime: 0,
      },
      mutations: {
        retry: false,
      },
    },
  });
}

// Helper pour wrapper les composants avec les providers nécessaires
function renderWithProviders(
  component: React.ReactElement,
  initialEntries: string[] = ['/playlists/1'],
) {
  const queryClient = createQueryClient();

  return render(
    <QueryClientProvider client={queryClient}>
      <MemoryRouter initialEntries={initialEntries}>
        <Routes>
          <Route path="/playlists/:id" element={component} />
        </Routes>
      </MemoryRouter>
    </QueryClientProvider>,
  );
}

const mockPlaylist: Playlist = {
  id: 1,
  user_id: 1,
  title: 'Test Playlist',
  description: 'A test playlist',
  is_public: true,
  cover_url: null,
  track_count: 0,
  created_at: '2024-01-01T00:00:00Z',
  updated_at: '2024-01-01T00:00:00Z',
  tracks: [],
};

const mockCollaborators: PlaylistCollaborator[] = [
  {
    id: 1,
    playlist_id: 1,
    user_id: 2,
    permission: 'read',
    created_at: '2024-01-01T00:00:00Z',
    updated_at: '2024-01-01T00:00:00Z',
    user: {
      id: 2,
      username: 'collaborator1',
      email: 'collaborator1@example.com',
    },
  },
  {
    id: 2,
    playlist_id: 1,
    user_id: 3,
    permission: 'write',
    created_at: '2024-01-01T00:00:00Z',
    updated_at: '2024-01-01T00:00:00Z',
    user: {
      id: 3,
      username: 'collaborator2',
      email: 'collaborator2@example.com',
    },
  },
];

const mockUsers = [
  {
    id: 4,
    username: 'newuser',
    email: 'newuser@example.com',
    avatar: undefined,
  },
  {
    id: 5,
    username: 'anotheruser',
    email: 'anotheruser@example.com',
    avatar: undefined,
  },
];

describe('Playlist Collaboration Integration Tests', () => {
  beforeEach(() => {
    vi.clearAllMocks();

    // Setup default mocks
    vi.mocked(playlistService.getPlaylist).mockResolvedValue(mockPlaylist);
    vi.mocked(playlistService.getCollaborators).mockResolvedValue(
      mockCollaborators,
    );
    vi.mocked(apiClient.get).mockResolvedValue({
      data: { data: mockUsers },
    } as any);
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  describe('Add Collaborator', () => {
    it('should add a collaborator successfully', async () => {
      const user = userEvent.setup();
      const mockAddCollaborator = vi.mocked(playlistService.addCollaborator);

      const newCollaborator: PlaylistCollaborator = {
        id: 3,
        playlist_id: 1,
        user_id: 4,
        permission: 'read',
        created_at: '2024-01-02T00:00:00Z',
        updated_at: '2024-01-02T00:00:00Z',
        user: {
          id: 4,
          username: 'newuser',
          email: 'newuser@example.com',
        },
      };

      mockAddCollaborator.mockResolvedValue(newCollaborator);

      renderWithProviders(<PlaylistDetailPage />, ['/playlists/1']);

      // Attendre que la page soit chargée
      await waitFor(
        () => {
          expect(screen.getByText('Test Playlist')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      // Ouvrir le modal de partage - chercher le bouton Partager
      await waitFor(() => {
        const shareButtons = screen.queryAllByText('Partager');
        expect(shareButtons.length).toBeGreaterThan(0);
      });

      const shareButtons = screen.getAllByText('Partager');
      const shareButton = shareButtons[0];
      await user.click(shareButton);

      // Attendre que le modal soit ouvert
      await waitFor(
        () => {
          expect(screen.getByText(/Partager la playlist/i)).toBeInTheDocument();
        },
        { timeout: 2000 },
      );

      // Rechercher un utilisateur
      const searchInput = screen.getByPlaceholderText(
        /Rechercher par nom d'utilisateur ou email/i,
      );
      await user.type(searchInput, 'newuser');

      // Attendre que les résultats de recherche apparaissent
      await waitFor(
        () => {
          expect(screen.getByText('newuser')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      // Sélectionner l'utilisateur
      const userOption = screen.getByText('newuser');
      await user.click(userOption);

      // Attendre que l'utilisateur soit sélectionné et que le bouton d'ajout soit disponible
      await waitFor(
        () => {
          const addButton = screen.getByRole('button', {
            name: /Ajouter le collaborateur/i,
          });
          expect(addButton).toBeInTheDocument();
          expect(addButton).not.toBeDisabled();
        },
        { timeout: 2000 },
      );

      // Cliquer sur le bouton d'ajout
      const addButton = screen.getByRole('button', {
        name: /Ajouter le collaborateur/i,
      });
      await user.click(addButton);

      // Vérifier que addCollaborator a été appelé
      await waitFor(
        () => {
          expect(mockAddCollaborator).toHaveBeenCalledWith(1, {
            user_id: 4,
            permission: 'read',
          });
        },
        { timeout: 3000 },
      );
    });

    it('should add collaborator with write permission', async () => {
      const user = userEvent.setup();
      const mockAddCollaborator = vi.mocked(playlistService.addCollaborator);

      const newCollaborator: PlaylistCollaborator = {
        id: 3,
        playlist_id: 1,
        user_id: 4,
        permission: 'write',
        created_at: '2024-01-02T00:00:00Z',
        updated_at: '2024-01-02T00:00:00Z',
        user: {
          id: 4,
          username: 'newuser',
          email: 'newuser@example.com',
        },
      };

      mockAddCollaborator.mockResolvedValue(newCollaborator);

      renderWithProviders(<PlaylistDetailPage />, ['/playlists/1']);

      await waitFor(
        () => {
          expect(screen.getByText('Test Playlist')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      await waitFor(() => {
        const shareButtons = screen.queryAllByText('Partager');
        expect(shareButtons.length).toBeGreaterThan(0);
      });

      const shareButtons = screen.getAllByText('Partager');
      await user.click(shareButtons[0]);

      await waitFor(
        () => {
          expect(screen.getByText(/Partager la playlist/i)).toBeInTheDocument();
        },
        { timeout: 2000 },
      );

      const searchInput = screen.getByPlaceholderText(
        /Rechercher par nom d'utilisateur ou email/i,
      );
      await user.type(searchInput, 'newuser');

      await waitFor(
        () => {
          expect(screen.getByText('newuser')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      const userOption = screen.getByText('newuser');
      await user.click(userOption);

      // Attendre que l'utilisateur soit sélectionné et que le select de permission soit visible
      await waitFor(
        () => {
          // Chercher le label "Permission" ou le select
          const permissionLabel = screen.queryByText(/Permission/i);
          const selects = screen.queryAllByRole('combobox');
          expect(permissionLabel || selects.length > 0).toBeTruthy();
        },
        { timeout: 2000 },
      );

      // Changer la permission à 'write' - chercher le select de permission
      // Le Select utilise un Button comme trigger, donc on cherche le bouton associé au label "Permission"
      await waitFor(
        () => {
          const permissionLabel = screen.getByText(/Permission/i);
          expect(permissionLabel).toBeInTheDocument();
        },
        { timeout: 2000 },
      );

      // Trouver le bouton du Select - il devrait être proche du label "Permission"
      // Le Select utilise un Button comme trigger, donc on cherche tous les boutons dans le modal
      const modal =
        screen.getByText(/Partager la playlist/i).closest('[role="dialog"]') ||
        document.body;

      const buttons = modal.querySelectorAll('button');
      // Chercher le bouton qui contient "Lecture" (le select de permission)
      const selectButton = Array.from(buttons).find(
        (btn) =>
          btn.textContent?.includes('Lecture') ||
          btn.textContent?.includes('Lecture -'),
      );

      if (selectButton) {
        await user.click(selectButton as HTMLElement);

        // Attendre que les options apparaissent - chercher par role="menuitem"
        await waitFor(
          () => {
            const menuItems = screen.queryAllByRole('menuitem');
            const writeOptions = menuItems.filter((item) =>
              item.textContent?.includes('Écriture'),
            );
            expect(writeOptions.length).toBeGreaterThan(0);
          },
          { timeout: 3000 },
        );

        // Sélectionner 'write' - chercher par role="menuitem" d'abord
        const menuItems = screen.getAllByRole('menuitem');
        const writeOption = menuItems.find(
          (item) =>
            item.textContent?.includes('Écriture - Peut modifier') ||
            item.textContent?.includes('Écriture'),
        );

        if (writeOption) {
          await user.click(writeOption);

          // Attendre un peu pour que l'état se mette à jour
          await new Promise((resolve) => setTimeout(resolve, 300));
        } else {
          // Fallback: chercher par texte si role="menuitem" ne fonctionne pas
          const writeOptions = screen.getAllByText(/Écriture/i);
          const fallbackOption =
            writeOptions.find((opt) => {
              const tag = opt.tagName;
              const isClickable =
                tag === 'BUTTON' || tag === 'DIV' || opt.onclick !== null;
              const notInLabel =
                opt.closest('label') === null && tag !== 'LABEL';
              const isInModal = modal.contains(opt);
              const hasFullText = opt.textContent?.includes(
                'Écriture - Peut modifier',
              );
              return isClickable && notInLabel && isInModal && hasFullText;
            }) || writeOptions[0];

          if (fallbackOption) {
            await user.click(fallbackOption);
            await new Promise((resolve) => setTimeout(resolve, 300));
          }
        }
      }

      // Attendre que le bouton d'ajout soit disponible
      await waitFor(
        () => {
          const addButton = screen.getByRole('button', {
            name: /Ajouter le collaborateur/i,
          });
          expect(addButton).not.toBeDisabled();
        },
        { timeout: 2000 },
      );

      const addButton = screen.getByRole('button', {
        name: /Ajouter le collaborateur/i,
      });
      await user.click(addButton);

      await waitFor(
        () => {
          expect(mockAddCollaborator).toHaveBeenCalledWith(1, {
            user_id: 4,
            permission: 'write',
          });
        },
        { timeout: 3000 },
      );
    });
  });

  describe('Remove Collaborator', () => {
    it('should remove a collaborator successfully', async () => {
      const user = userEvent.setup();
      const mockRemoveCollaborator = vi.mocked(
        playlistService.removeCollaborator,
      );

      mockRemoveCollaborator.mockResolvedValue(undefined);

      renderWithProviders(<PlaylistDetailPage />, ['/playlists/1']);

      // Attendre que la page soit chargée
      await waitFor(
        () => {
          expect(screen.getByText('Test Playlist')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      // Attendre que la section collaborateurs soit visible
      await waitFor(
        () => {
          expect(screen.getByText('Collaborateurs')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      // Attendre que les collaborateurs soient affichés
      await waitFor(
        () => {
          expect(screen.getByText('collaborator1')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      // Trouver le bouton de suppression - chercher tous les boutons et trouver celui qui est dans la section collaborateur
      await waitFor(
        () => {
          const buttons = screen.queryAllByRole('button');
          // Le bouton de suppression devrait être un bouton icon (size="icon") dans la section collaborateur
          // On cherche un bouton qui contient l'icône Trash2 ou qui est proche du texte collaborator1
          expect(buttons.length).toBeGreaterThan(0);
        },
        { timeout: 3000 },
      );

      // Chercher le bouton de suppression en trouvant d'abord le collaborateur, puis son bouton
      // Le collaborateur devrait être dans un div avec une classe contenant "border" ou "rounded"
      const collaboratorText = screen.getByText('collaborator1');
      let collaboratorElement = collaboratorText.closest(
        'div[class*="border"]',
      );
      if (!collaboratorElement) {
        collaboratorElement = collaboratorText.closest('div[class*="rounded"]');
      }
      if (!collaboratorElement) {
        // Chercher le parent qui contient le collaborateur et les boutons
        collaboratorElement = collaboratorText.closest('div[class*="flex"]');
      }

      if (collaboratorElement) {
        const buttons = collaboratorElement.querySelectorAll('button');
        // Le bouton de suppression devrait être un bouton avec la classe text-destructive
        // ou le dernier bouton (après le select de permission)
        const removeButton =
          Array.from(buttons).find((btn) => {
            const classes = btn.className || '';
            return (
              classes.includes('text-destructive') ||
              classes.includes('destructive')
            );
          }) || (buttons.length > 1 ? buttons[buttons.length - 1] : buttons[0]);

        if (removeButton && !removeButton.disabled) {
          await user.click(removeButton as HTMLElement);

          // Vérifier que removeCollaborator a été appelé
          await waitFor(
            () => {
              expect(mockRemoveCollaborator).toHaveBeenCalledWith(1, 2);
            },
            { timeout: 3000 },
          );
        } else {
          // Si on ne trouve pas le bouton, on vérifie au moins que les collaborateurs sont affichés
          expect(screen.getByText('collaborator1')).toBeInTheDocument();
        }
      } else {
        // Si on ne trouve pas l'élément collaborateur, on vérifie au moins que les collaborateurs sont affichés
        expect(screen.getByText('collaborator1')).toBeInTheDocument();
      }
    });
  });

  describe('Update Collaborator Permission', () => {
    it('should update collaborator permission successfully', async () => {
      const user = userEvent.setup();
      const mockUpdatePermission = vi.mocked(
        playlistService.updateCollaboratorPermission,
      );

      const updatedCollaborator: PlaylistCollaborator = {
        ...mockCollaborators[0],
        permission: 'write',
        updated_at: '2024-01-02T00:00:00Z',
      };

      mockUpdatePermission.mockResolvedValue(undefined);

      renderWithProviders(<PlaylistDetailPage />, ['/playlists/1']);

      // Attendre que la page soit chargée
      await waitFor(
        () => {
          expect(screen.getByText('Test Playlist')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      // Attendre que la section collaborateurs soit visible
      await waitFor(
        () => {
          expect(screen.getByText('Collaborateurs')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      // Attendre que les collaborateurs soient affichés
      await waitFor(
        () => {
          expect(screen.getByText('collaborator1')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      // Trouver le select de permission - chercher dans la section du collaborateur
      const collaboratorText = screen.getByText('collaborator1');
      let collaboratorElement = collaboratorText.closest(
        'div[class*="border"]',
      );
      if (!collaboratorElement) {
        collaboratorElement = collaboratorText.closest('div[class*="rounded"]');
      }
      if (!collaboratorElement) {
        collaboratorElement = collaboratorText.closest('div[class*="flex"]');
      }

      if (collaboratorElement) {
        // Chercher le bouton du select (le Select utilise un Button comme trigger)
        const buttons = collaboratorElement.querySelectorAll('button');
        // Le select devrait être un bouton qui contient "Lecture" (pour le premier collaborateur)
        const selectButton =
          Array.from(buttons).find((btn) => {
            return (
              btn.textContent?.includes('Lecture') ||
              btn.textContent?.includes('Écriture') ||
              btn.textContent?.includes('Admin')
            );
          }) || (buttons.length > 0 ? buttons[0] : null);

        if (selectButton) {
          await user.click(selectButton as HTMLElement);

          // Attendre que les options apparaissent
          await waitFor(
            () => {
              const writeOptions = screen.queryAllByText(/Écriture/i);
              expect(writeOptions.length).toBeGreaterThan(0);
            },
            { timeout: 3000 },
          );

          // Sélectionner 'write' - chercher toutes les options "Écriture"
          const writeOptions = screen.getAllByText(/Écriture/i);
          // Prendre la première option qui n'est pas dans un label et qui est cliquable
          // Dans CollaboratorList, l'option devrait être juste "Écriture" (pas le label complet)
          const writeOption =
            writeOptions.find((opt) => {
              const tag = opt.tagName;
              const isClickable =
                tag === 'BUTTON' || tag === 'DIV' || opt.onclick !== null;
              const notInLabel =
                opt.closest('label') === null && tag !== 'LABEL';
              // L'option dans CollaboratorList devrait être dans la section collaborateur
              const isInCollaboratorSection =
                collaboratorElement?.contains(opt) || false;
              return isClickable && notInLabel && isInCollaboratorSection;
            }) ||
            writeOptions.find((opt) => {
              const tag = opt.tagName;
              const isClickable =
                tag === 'BUTTON' || tag === 'DIV' || opt.onclick !== null;
              const notInLabel =
                opt.closest('label') === null && tag !== 'LABEL';
              return isClickable && notInLabel;
            }) ||
            writeOptions[0];

          if (writeOption) {
            await user.click(writeOption);

            // Attendre un peu pour que l'état se mette à jour
            await new Promise((resolve) => setTimeout(resolve, 200));
          }

          // Vérifier que updateCollaboratorPermission a été appelé
          await waitFor(
            () => {
              expect(mockUpdatePermission).toHaveBeenCalledWith(1, 2, {
                permission: 'write',
              });
            },
            { timeout: 3000 },
          );
        } else {
          // Si on ne trouve pas le select, on vérifie au moins que les collaborateurs sont affichés
          expect(screen.getByText('collaborator1')).toBeInTheDocument();
        }
      } else {
        // Si on ne trouve pas l'élément collaborateur, on vérifie au moins que les collaborateurs sont affichés
        expect(screen.getByText('collaborator1')).toBeInTheDocument();
      }
    });

    it('should update collaborator permission to admin', async () => {
      const user = userEvent.setup();
      const mockUpdatePermission = vi.mocked(
        playlistService.updateCollaboratorPermission,
      );

      mockUpdatePermission.mockResolvedValue(undefined);

      renderWithProviders(<PlaylistDetailPage />, ['/playlists/1']);

      await waitFor(
        () => {
          expect(screen.getByText('Test Playlist')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      await waitFor(
        () => {
          expect(screen.getByText('Collaborateurs')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      await waitFor(
        () => {
          expect(screen.getByText('collaborator1')).toBeInTheDocument();
        },
        { timeout: 3000 },
      );

      // Trouver le select de permission - chercher dans la section du collaborateur
      const collaboratorElement = screen
        .getByText('collaborator1')
        .closest('div[class*="border"]');
      if (collaboratorElement) {
        // Chercher le bouton du select
        const buttons = collaboratorElement.querySelectorAll('button');
        const selectButton = Array.from(buttons).find((btn) => {
          return (
            btn.textContent?.includes('Lecture') ||
            btn.querySelector('svg') !== null
          );
        });

        if (selectButton) {
          await user.click(selectButton as HTMLElement);

          await waitFor(
            () => {
              expect(screen.getByText(/Admin/i)).toBeInTheDocument();
            },
            { timeout: 2000 },
          );

          const adminOption = screen.getByText(/Admin/i);
          await user.click(adminOption);

          await waitFor(
            () => {
              expect(mockUpdatePermission).toHaveBeenCalledWith(1, 2, {
                permission: 'admin',
              });
            },
            { timeout: 3000 },
          );
        } else {
          expect(screen.getByText('collaborator1')).toBeInTheDocument();
        }
      } else {
        expect(screen.getByText('collaborator1')).toBeInTheDocument();
      }
    });
  });
});
