import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TrackEdit } from './TrackEdit';
import {
  getTrack,
  updateTrack,
  TrackUploadError,
} from '../services/trackService';
import { useToast } from '@/hooks/useToast';
import type { Track } from '../types/track';

// Mock dependencies
vi.mock('../services/trackService');
vi.mock('@/hooks/useToast');

describe('TrackEdit', () => {
  const mockToast = {
    success: vi.fn(),
    error: vi.fn(),
    warning: vi.fn(),
    info: vi.fn(),
    toast: vi.fn(),
  };

  const mockTrack: Track = {
    id: 1,
    creator_id: 123,
    title: 'Original Title',
    artist: 'Original Artist',
    album: 'Original Album',
    genre: 'Rock',
    year: 2020,
    duration: 180,
    file_path: '/uploads/track.mp3',
    file_size: 1024,
    format: 'MP3',
    is_public: false,
    play_count: 10,
    like_count: 5,
    created_at: '2024-01-01T00:00:00Z',
    updated_at: '2024-01-01T00:00:00Z',
  };

  const mockOnSave = vi.fn();
  const mockOnCancel = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(useToast).mockReturnValue(mockToast);
  });

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

  it('should render loading state initially', async () => {
    vi.mocked(getTrack).mockImplementation(
      () =>
        new Promise((resolve) => {
          setTimeout(() => resolve(mockTrack), 100);
        }),
    );

    render(<TrackEdit trackId={1} />);

    expect(screen.getByText(/chargement du track/i)).toBeInTheDocument();
  });

  it('should render form with track data after loading', async () => {
    vi.mocked(getTrack).mockResolvedValue(mockTrack);

    render(<TrackEdit trackId={1} />);

    await waitFor(() => {
      expect(screen.getByDisplayValue('Original Title')).toBeInTheDocument();
      expect(screen.getByDisplayValue('Original Artist')).toBeInTheDocument();
      expect(screen.getByDisplayValue('Original Album')).toBeInTheDocument();
      expect(screen.getByDisplayValue('Rock')).toBeInTheDocument();
    });
  });

  it('should display error message on load failure', async () => {
    const error = new TrackUploadError('Track not found', 'VALIDATION', false);
    vi.mocked(getTrack).mockRejectedValue(error);

    render(<TrackEdit trackId={1} />);

    await waitFor(() => {
      expect(screen.getByText(/track not found/i)).toBeInTheDocument();
      expect(mockToast.error).toHaveBeenCalledWith('Track not found');
    });
  });

  it('should validate required title field', async () => {
    vi.mocked(getTrack).mockResolvedValue(mockTrack);

    render(<TrackEdit trackId={1} />);

    await waitFor(() => {
      expect(screen.getByDisplayValue('Original Title')).toBeInTheDocument();
    });

    const titleInput = screen.getByLabelText(/titre/i);
    await userEvent.clear(titleInput);

    const submitButton = screen.getByRole('button', { name: /enregistrer/i });
    await userEvent.click(submitButton);

    await waitFor(() => {
      expect(screen.getByText(/le titre est requis/i)).toBeInTheDocument();
    });

    expect(updateTrack).not.toHaveBeenCalled();
  });

  it('should submit form with updated data', async () => {
    const updatedTrack = {
      ...mockTrack,
      title: 'Updated Title',
      genre: 'Jazz',
    };
    vi.mocked(getTrack).mockResolvedValue(mockTrack);
    vi.mocked(updateTrack).mockResolvedValue(updatedTrack);

    render(<TrackEdit trackId={1} onSave={mockOnSave} />);

    await waitFor(() => {
      expect(screen.getByDisplayValue('Original Title')).toBeInTheDocument();
    });

    const titleInput = screen.getByLabelText(/titre/i);
    await userEvent.clear(titleInput);
    await userEvent.type(titleInput, 'Updated Title');

    const genreInput = screen.getByLabelText(/genre/i);
    await userEvent.clear(genreInput);
    await userEvent.type(genreInput, 'Jazz');

    const submitButton = screen.getByRole('button', { name: /enregistrer/i });
    await userEvent.click(submitButton);

    await waitFor(() => {
      expect(updateTrack).toHaveBeenCalledWith(1, {
        title: 'Updated Title',
        artist: 'Original Artist',
        album: 'Original Album',
        genre: 'Jazz',
        year: 2020,
        is_public: false,
      });
      expect(mockToast.success).toHaveBeenCalledWith(
        'Track mis à jour avec succès',
      );
      expect(mockOnSave).toHaveBeenCalledWith(updatedTrack);
    });
  });

  it('should handle update error', async () => {
    vi.mocked(getTrack).mockResolvedValue(mockTrack);
    const error = new TrackUploadError('Update failed', 'SERVER', true);
    vi.mocked(updateTrack).mockRejectedValue(error);

    render(<TrackEdit trackId={1} />);

    await waitFor(() => {
      expect(screen.getByDisplayValue('Original Title')).toBeInTheDocument();
    });

    const submitButton = screen.getByRole('button', { name: /enregistrer/i });
    await userEvent.click(submitButton);

    await waitFor(() => {
      expect(mockToast.error).toHaveBeenCalledWith('Update failed');
    });
  });

  it('should toggle is_public checkbox', async () => {
    vi.mocked(getTrack).mockResolvedValue(mockTrack);

    render(<TrackEdit trackId={1} />);

    await waitFor(() => {
      expect(screen.getByDisplayValue('Original Title')).toBeInTheDocument();
    });

    const publicCheckbox = screen.getByLabelText(/track public/i);
    expect(publicCheckbox).not.toBeChecked();

    await userEvent.click(publicCheckbox);

    expect(publicCheckbox).toBeChecked();
  });

  it('should call onCancel when cancel button is clicked', async () => {
    vi.mocked(getTrack).mockResolvedValue(mockTrack);

    render(<TrackEdit trackId={1} onCancel={mockOnCancel} />);

    await waitFor(() => {
      expect(screen.getByDisplayValue('Original Title')).toBeInTheDocument();
    });

    const cancelButton = screen.getByRole('button', { name: /annuler/i });
    await userEvent.click(cancelButton);

    expect(mockOnCancel).toHaveBeenCalled();
  });

  it('should disable submit button when form is not dirty', async () => {
    vi.mocked(getTrack).mockResolvedValue(mockTrack);

    render(<TrackEdit trackId={1} />);

    await waitFor(() => {
      expect(screen.getByDisplayValue('Original Title')).toBeInTheDocument();
    });

    const submitButton = screen.getByRole('button', { name: /enregistrer/i });
    expect(submitButton).toBeDisabled();
  });

  it('should enable submit button when form is dirty', async () => {
    vi.mocked(getTrack).mockResolvedValue(mockTrack);

    render(<TrackEdit trackId={1} />);

    await waitFor(() => {
      expect(screen.getByDisplayValue('Original Title')).toBeInTheDocument();
    });

    const titleInput = screen.getByLabelText(/titre/i);
    await userEvent.type(titleInput, ' Updated');

    const submitButton = screen.getByRole('button', { name: /enregistrer/i });
    expect(submitButton).not.toBeDisabled();
  });

  it('should handle year field correctly', async () => {
    vi.mocked(getTrack).mockResolvedValue(mockTrack);
    const updatedTrack = { ...mockTrack, year: 2021 };
    vi.mocked(updateTrack).mockResolvedValue(updatedTrack);

    render(<TrackEdit trackId={1} />);

    await waitFor(() => {
      expect(screen.getByDisplayValue('Original Title')).toBeInTheDocument();
    });

    const yearInput = screen.getByLabelText(/année/i);
    await userEvent.clear(yearInput);
    await userEvent.type(yearInput, '2021');

    const submitButton = screen.getByRole('button', { name: /enregistrer/i });
    await userEvent.click(submitButton);

    await waitFor(() => {
      expect(updateTrack).toHaveBeenCalledWith(
        1,
        expect.objectContaining({
          year: 2021,
        }),
      );
    });
  });
});
