import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { useHLSStream } from './useHLSStream';
import { getHLSStreamStatus } from '../services/hlsService';
import type { HLSStreamStatus } from '../services/hlsService';

// Mock the service
vi.mock('../services/hlsService', () => ({
  getHLSStreamStatus: vi.fn(),
}));

const mockGetHLSStreamStatus = vi.mocked(getHLSStreamStatus);

describe('useHLSStream', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

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

  it('should initialize with loading state', () => {
    mockGetHLSStreamStatus.mockImplementation(
      () => new Promise(() => {}), // Never resolves to keep loading
    );

    const { result } = renderHook(() => useHLSStream(123));

    expect(result.current.loading).toBe(true);
    expect(result.current.status).toBe(null);
    expect(result.current.error).toBe(null);
    expect(result.current.isReady).toBe(false);
    expect(result.current.isProcessing).toBe(false);
  });

  it('should fetch stream status on mount', async () => {
    const mockStatus: HLSStreamStatus = {
      status: 'ready',
      bitrates: [128, 192, 320],
      segments_count: 10,
      playlist_url: 'track_123/master.m3u8',
      track_id: 123,
    };

    mockGetHLSStreamStatus.mockResolvedValue(mockStatus);

    const { result } = renderHook(() => useHLSStream(123));

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    expect(mockGetHLSStreamStatus).toHaveBeenCalledWith(123);
    expect(result.current.status).toEqual(mockStatus);
    expect(result.current.error).toBe(null);
    expect(result.current.isReady).toBe(true);
    expect(result.current.isProcessing).toBe(false);
  });

  it('should handle error state', async () => {
    const mockError = new Error('Stream not found');
    mockGetHLSStreamStatus.mockRejectedValue(mockError);

    const { result } = renderHook(() => useHLSStream(123));

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    expect(result.current.status).toBe(null);
    expect(result.current.error).toEqual(mockError);
    expect(result.current.isReady).toBe(false);
    expect(result.current.isProcessing).toBe(false);
  });

  it('should set isReady to true when status is ready', async () => {
    const mockStatus: HLSStreamStatus = {
      status: 'ready',
      bitrates: [128],
      segments_count: 5,
      playlist_url: 'track_123/master.m3u8',
      track_id: 123,
    };

    mockGetHLSStreamStatus.mockResolvedValue(mockStatus);

    const { result } = renderHook(() => useHLSStream(123));

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    expect(result.current.isReady).toBe(true);
    expect(result.current.isProcessing).toBe(false);
  });

  it('should set isProcessing to true when status is processing', async () => {
    const mockStatus: HLSStreamStatus = {
      status: 'processing',
      bitrates: [],
      segments_count: 0,
      playlist_url: '',
      track_id: 123,
      queue_job_id: 1,
      retry_count: 0,
    };

    mockGetHLSStreamStatus.mockResolvedValue(mockStatus);

    const { result } = renderHook(() => useHLSStream(123));

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    expect(result.current.isReady).toBe(false);
    expect(result.current.isProcessing).toBe(true);
  });

  it('should refetch when trackId changes', async () => {
    const mockStatus1: HLSStreamStatus = {
      status: 'ready',
      bitrates: [128],
      segments_count: 5,
      playlist_url: 'track_123/master.m3u8',
      track_id: 123,
    };

    const mockStatus2: HLSStreamStatus = {
      status: 'ready',
      bitrates: [192],
      segments_count: 8,
      playlist_url: 'track_456/master.m3u8',
      track_id: 456,
    };

    mockGetHLSStreamStatus
      .mockResolvedValueOnce(mockStatus1)
      .mockResolvedValueOnce(mockStatus2);

    const { result, rerender } = renderHook(
      ({ trackId }) => useHLSStream(trackId),
      {
        initialProps: { trackId: 123 },
      },
    );

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    expect(result.current.status?.track_id).toBe(123);
    expect(mockGetHLSStreamStatus).toHaveBeenCalledTimes(1);

    // Change trackId
    rerender({ trackId: 456 });

    // Should be loading again
    expect(result.current.loading).toBe(true);

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    expect(result.current.status?.track_id).toBe(456);
    expect(mockGetHLSStreamStatus).toHaveBeenCalledTimes(2);
    expect(mockGetHLSStreamStatus).toHaveBeenLastCalledWith(456);
  });

  it('should cleanup on unmount', async () => {
    let resolvePromise: (value: HLSStreamStatus) => void;
    const promise = new Promise<HLSStreamStatus>((resolve) => {
      resolvePromise = resolve;
    });

    mockGetHLSStreamStatus.mockReturnValue(promise);

    const { result, unmount } = renderHook(() => useHLSStream(123));

    expect(result.current.loading).toBe(true);

    // Unmount before promise resolves
    unmount();

    // Resolve the promise after unmount
    resolvePromise!({
      status: 'ready',
      bitrates: [128],
      segments_count: 5,
      playlist_url: 'track_123/master.m3u8',
      track_id: 123,
    });

    // Wait a bit to ensure the promise resolves
    await new Promise((resolve) => setTimeout(resolve, 10));

    // The status should not be updated because component is unmounted
    // (We can't directly test this, but the cleanup function should prevent state updates)
    expect(mockGetHLSStreamStatus).toHaveBeenCalledWith(123);
  });

  it('should handle pending status', async () => {
    const mockStatus: HLSStreamStatus = {
      status: 'pending',
      bitrates: [],
      segments_count: 0,
      playlist_url: '',
      track_id: 123,
    };

    mockGetHLSStreamStatus.mockResolvedValue(mockStatus);

    const { result } = renderHook(() => useHLSStream(123));

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    expect(result.current.isReady).toBe(false);
    expect(result.current.isProcessing).toBe(false);
    expect(result.current.status?.status).toBe('pending');
  });

  it('should handle failed status', async () => {
    const mockStatus: HLSStreamStatus = {
      status: 'failed',
      bitrates: [],
      segments_count: 0,
      playlist_url: '',
      track_id: 123,
    };

    mockGetHLSStreamStatus.mockResolvedValue(mockStatus);

    const { result } = renderHook(() => useHLSStream(123));

    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    expect(result.current.isReady).toBe(false);
    expect(result.current.isProcessing).toBe(false);
    expect(result.current.status?.status).toBe('failed');
  });
});
