import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { useBitrateAdaptation } from './useBitrateAdaptation';
import { adaptBitrate } from '../services/bitrateService';

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

const mockAdaptBitrate = vi.mocked(adaptBitrate);

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

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

  it('should initialize with current bitrate', () => {
    const { result } = renderHook(() => useBitrateAdaptation(123, 128));

    expect(result.current.recommendedBitrate).toBe(128);
    expect(result.current.loading).toBe(false);
    expect(result.current.error).toBe(null);
    expect(result.current.isAdapting).toBe(false);
    expect(result.current.hasBitrateChanged).toBe(false);
  });

  it('should update recommended bitrate when current bitrate changes', () => {
    const { result, rerender } = renderHook(
      ({ currentBitrate }) => useBitrateAdaptation(123, currentBitrate),
      { initialProps: { currentBitrate: 128 } },
    );

    expect(result.current.recommendedBitrate).toBe(128);

    rerender({ currentBitrate: 192 });

    expect(result.current.recommendedBitrate).toBe(192);
    expect(result.current.hasBitrateChanged).toBe(false);
  });

  it('should call adaptBitrate service and update recommended bitrate', async () => {
    const mockResponse = {
      recommended_bitrate: 320,
    };

    mockAdaptBitrate.mockResolvedValue(mockResponse);

    const { result } = renderHook(() => useBitrateAdaptation(123, 128));

    await act(async () => {
      await result.current.checkAndAdapt({
        bandwidth: 10485760,
        buffer_level: 0.5,
      });
    });

    expect(mockAdaptBitrate).toHaveBeenCalledWith(123, {
      current_bitrate: 128,
      bandwidth: 10485760,
      buffer_level: 0.5,
    });

    await waitFor(() => {
      expect(result.current.recommendedBitrate).toBe(320);
      expect(result.current.hasBitrateChanged).toBe(true);
      expect(result.current.loading).toBe(false);
      expect(result.current.error).toBe(null);
    });
  });

  it('should set loading state during adaptation', async () => {
    let resolvePromise: (value: any) => void;
    const promise = new Promise((resolve) => {
      resolvePromise = resolve;
    });

    mockAdaptBitrate.mockReturnValue(promise as any);

    const { result } = renderHook(() => useBitrateAdaptation(123, 128));

    act(() => {
      result.current.checkAndAdapt({
        bandwidth: 10485760,
        buffer_level: 0.5,
      });
    });

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

    await act(async () => {
      resolvePromise!({
        recommended_bitrate: 320,
      });
      await promise;
    });

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

  it('should handle errors during adaptation', async () => {
    const mockError = new Error('Network error');
    mockAdaptBitrate.mockRejectedValue(mockError);

    const { result } = renderHook(() => useBitrateAdaptation(123, 128));

    await act(async () => {
      await result.current.checkAndAdapt({
        bandwidth: 10485760,
        buffer_level: 0.5,
      });
    });

    await waitFor(() => {
      expect(result.current.error).toEqual(mockError);
      expect(result.current.loading).toBe(false);
      expect(result.current.isAdapting).toBe(false);
      expect(result.current.recommendedBitrate).toBe(128); // Should remain unchanged
    });
  });

  it('should handle non-Error exceptions', async () => {
    mockAdaptBitrate.mockRejectedValue('String error');

    const { result } = renderHook(() => useBitrateAdaptation(123, 128));

    await act(async () => {
      await result.current.checkAndAdapt({
        bandwidth: 10485760,
        buffer_level: 0.5,
      });
    });

    await waitFor(() => {
      expect(result.current.error).toBeInstanceOf(Error);
      expect(result.current.error?.message).toBe('Bitrate adaptation failed');
    });
  });

  it('should clear error when clearError is called', async () => {
    const mockError = new Error('Network error');
    mockAdaptBitrate.mockRejectedValue(mockError);

    const { result } = renderHook(() => useBitrateAdaptation(123, 128));

    await act(async () => {
      await result.current.checkAndAdapt({
        bandwidth: 10485760,
        buffer_level: 0.5,
      });
    });

    await waitFor(() => {
      expect(result.current.error).toEqual(mockError);
    });

    act(() => {
      result.current.clearError();
    });

    expect(result.current.error).toBe(null);
  });

  it('should detect bitrate change correctly', async () => {
    const mockResponse = {
      recommended_bitrate: 192,
    };

    mockAdaptBitrate.mockResolvedValue(mockResponse);

    const { result } = renderHook(() => useBitrateAdaptation(123, 128));

    expect(result.current.hasBitrateChanged).toBe(false);

    await act(async () => {
      await result.current.checkAndAdapt({
        bandwidth: 500000,
        buffer_level: 0.5,
      });
    });

    await waitFor(() => {
      expect(result.current.hasBitrateChanged).toBe(true);
      expect(result.current.recommendedBitrate).toBe(192);
    });
  });

  it('should not update state if component is unmounted', async () => {
    let resolvePromise: (value: any) => void;
    const promise = new Promise((resolve) => {
      resolvePromise = resolve;
    });

    mockAdaptBitrate.mockReturnValue(promise as any);

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

    act(() => {
      result.current.checkAndAdapt({
        bandwidth: 10485760,
        buffer_level: 0.5,
      });
    });

    // Unmount before promise resolves
    unmount();

    // Resolve the promise after unmount
    await act(async () => {
      resolvePromise!({
        recommended_bitrate: 320,
      });
      await promise;
    });

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

    // The state should not be updated because component is unmounted
    // (We can't directly test this, but the cancelledRef should prevent state updates)
    expect(mockAdaptBitrate).toHaveBeenCalled();
  });

  it('should use correct trackId and currentBitrate in request', async () => {
    const mockResponse = {
      recommended_bitrate: 320,
    };

    mockAdaptBitrate.mockResolvedValue(mockResponse);

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

    await act(async () => {
      await result.current.checkAndAdapt({
        bandwidth: 10485760,
        buffer_level: 0.5,
      });
    });

    expect(mockAdaptBitrate).toHaveBeenCalledWith(123, {
      current_bitrate: 128,
      bandwidth: 10485760,
      buffer_level: 0.5,
    });

    // Change trackId and currentBitrate
    rerender({ trackId: 456, currentBitrate: 192 });

    await act(async () => {
      await result.current.checkAndAdapt({
        bandwidth: 10485760,
        buffer_level: 0.5,
      });
    });

    expect(mockAdaptBitrate).toHaveBeenLastCalledWith(456, {
      current_bitrate: 192,
      bandwidth: 10485760,
      buffer_level: 0.5,
    });
  });

  it('should handle bitrate decrease', async () => {
    const mockResponse = {
      recommended_bitrate: 128,
    };

    mockAdaptBitrate.mockResolvedValue(mockResponse);

    const { result } = renderHook(() => useBitrateAdaptation(123, 320));

    await act(async () => {
      await result.current.checkAndAdapt({
        bandwidth: 200000,
        buffer_level: 0.5,
      });
    });

    await waitFor(() => {
      expect(result.current.recommendedBitrate).toBe(128);
      expect(result.current.hasBitrateChanged).toBe(true);
    });
  });

  it('should handle no bitrate change', async () => {
    const mockResponse = {
      recommended_bitrate: 128,
    };

    mockAdaptBitrate.mockResolvedValue(mockResponse);

    const { result } = renderHook(() => useBitrateAdaptation(123, 128));

    await act(async () => {
      await result.current.checkAndAdapt({
        bandwidth: 10485760,
        buffer_level: 0.15, // Low buffer prevents increase
      });
    });

    await waitFor(() => {
      expect(result.current.recommendedBitrate).toBe(128);
      expect(result.current.hasBitrateChanged).toBe(false);
    });
  });
});
