import { renderHook, act } from '@testing-library/react';
import axios from 'axios';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

import type { QualityMetrics } from '@/Components/ContentEditor/QualityScoreSidebar';

import { useQualityScore } from './useQualityScore';

vi.mock('axios');

describe('useQualityScore', () => {
  const mockMetrics: QualityMetrics = {
    flesch_kincaid_grade: 8.5,
    avg_sentence_length: 15.2,
    paragraph_count: 3,
    passive_voice_percentage: 10.5,
    word_count: 250,
    h1_count: 1,
    h2_count: 2,
    h3_count: 3,
    h4_count: 0,
    h5_count: 0,
    h6_count: 0,
    image_count: 2,
    internal_link_count: 5,
    external_link_count: 3,
  };

  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers();
  });

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

  it('starts with null metrics and not loading', () => {
    const { result } = renderHook(() =>
      useQualityScore({
        content: '',
        siteUrl: 'https://example.com',
        siteId: 1,
      }),
    );

    expect(result.current.metrics).toBeNull();
    expect(result.current.isLoading).toBe(false);
    expect(result.current.error).toBeNull();
  });

  it('sets loading state immediately when content is provided', () => {
    const { result } = renderHook(() =>
      useQualityScore({
        content: '<p>Test content</p>',
        siteUrl: 'https://example.com',
        siteId: 1,
      }),
    );

    expect(result.current.isLoading).toBe(true);
    expect(result.current.error).toBeNull();
  });

  it('does not fetch when content is empty', async () => {
    renderHook(() =>
      useQualityScore({
        content: '',
        siteUrl: 'https://example.com',
        siteId: 1,
      }),
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
    });

    expect(axios.post).not.toHaveBeenCalled();
  });

  it('does not fetch when content is whitespace only', async () => {
    renderHook(() =>
      useQualityScore({
        content: '   ',
        siteUrl: 'https://example.com',
        siteId: 1,
      }),
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
    });

    expect(axios.post).not.toHaveBeenCalled();
  });

  it('does not fetch when enabled is false', async () => {
    renderHook(() =>
      useQualityScore({
        content: '<p>Test content</p>',
        siteUrl: 'https://example.com',
        siteId: 1,
        enabled: false,
      }),
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
    });

    expect(axios.post).not.toHaveBeenCalled();
  });

  it('debounces API calls with default 500ms delay', async () => {
    vi.mocked(axios.post).mockResolvedValue({ data: mockMetrics });

    const { rerender } = renderHook(
      ({ content }) =>
        useQualityScore({
          content,
          siteUrl: 'https://example.com',
          siteId: 1,
        }),
      { initialProps: { content: '<p>Test 1</p>' } },
    );

    // Should not call immediately
    expect(axios.post).not.toHaveBeenCalled();

    // Update content before debounce timer expires
    rerender({ content: '<p>Test 2</p>' });
    rerender({ content: '<p>Test 3</p>' });

    // Advance timer past debounce delay
    await act(async () => {
      vi.advanceTimersByTime(500);
      await Promise.resolve(); // Flush promises
    });

    // Should only call once with the latest content
    expect(axios.post).toHaveBeenCalledTimes(1);
    expect(axios.post).toHaveBeenCalledWith(
      expect.any(String),
      {
        content: '<p>Test 3</p>',
        site_url: 'https://example.com',
      },
      expect.objectContaining({
        signal: expect.any(AbortSignal),
      }),
    );
  });

  it('uses custom debounce delay when provided', async () => {
    vi.mocked(axios.post).mockResolvedValue({ data: mockMetrics });

    renderHook(() =>
      useQualityScore({
        content: '<p>Test content</p>',
        siteUrl: 'https://example.com',
        siteId: 1,
        debounceMs: 1000,
      }),
    );

    // Should not call after 500ms
    await act(async () => {
      vi.advanceTimersByTime(500);
    });
    expect(axios.post).not.toHaveBeenCalled();

    // Should call after 1000ms
    await act(async () => {
      vi.advanceTimersByTime(500);
      await Promise.resolve();
    });
    expect(axios.post).toHaveBeenCalledTimes(1);
  });

  it('fetches quality metrics successfully', async () => {
    vi.mocked(axios.post).mockResolvedValue({ data: mockMetrics });

    const { result } = renderHook(() =>
      useQualityScore({
        content: '<p>Test content</p>',
        siteUrl: 'https://example.com',
        siteId: 1,
      }),
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
      await Promise.resolve();
    });

    expect(result.current.metrics).toEqual(mockMetrics);
    expect(result.current.isLoading).toBe(false);
    expect(result.current.error).toBeNull();
  });

  it('handles API errors gracefully', async () => {
    vi.mocked(axios.post).mockRejectedValue({
      response: {
        data: {
          message: 'Validation failed',
        },
      },
      isAxiosError: true,
    });

    vi.mocked(axios.isAxiosError).mockReturnValue(true);

    const { result } = renderHook(() =>
      useQualityScore({
        content: '<p>Test content</p>',
        siteUrl: 'https://example.com',
        siteId: 1,
      }),
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
      await Promise.resolve();
    });

    expect(result.current.metrics).toBeNull();
    expect(result.current.isLoading).toBe(false);
    expect(result.current.error).toBe('Validation failed');
  });

  it('handles network errors gracefully', async () => {
    vi.mocked(axios.post).mockRejectedValue(new Error('Network error'));
    vi.mocked(axios.isAxiosError).mockReturnValue(false);

    const { result } = renderHook(() =>
      useQualityScore({
        content: '<p>Test content</p>',
        siteUrl: 'https://example.com',
        siteId: 1,
      }),
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
      await Promise.resolve();
    });

    expect(result.current.metrics).toBeNull();
    expect(result.current.isLoading).toBe(false);
    expect(result.current.error).toBe('Failed to analyze content');
  });

  it('aborts pending requests on unmount', async () => {
    vi.mocked(axios.post).mockImplementation(() => {
      return new Promise(() => {}); // Never resolves
    });

    const { unmount } = renderHook(() =>
      useQualityScore({
        content: '<p>Test content</p>',
        siteUrl: 'https://example.com',
        siteId: 1,
      }),
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
    });

    expect(axios.post).toHaveBeenCalled();

    unmount();

    // Verify the request was made with an AbortSignal
    expect(axios.post).toHaveBeenCalledWith(
      expect.any(String),
      expect.any(Object),
      expect.objectContaining({
        signal: expect.any(AbortSignal),
      }),
    );
  });

  it('aborts pending requests when content changes', async () => {
    vi.mocked(axios.post).mockResolvedValue({ data: mockMetrics });

    const { rerender } = renderHook(
      ({ content }) =>
        useQualityScore({
          content,
          siteUrl: 'https://example.com',
          siteId: 1,
        }),
      { initialProps: { content: '<p>Test 1</p>' } },
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
      await Promise.resolve();
    });

    expect(axios.post).toHaveBeenCalledTimes(1);

    // Change content - should abort previous request
    rerender({ content: '<p>Test 2</p>' });

    await act(async () => {
      vi.advanceTimersByTime(500);
      await Promise.resolve();
    });

    expect(axios.post).toHaveBeenCalledTimes(2);
  });

  it('clears metrics and stops loading when enabled is set to false', async () => {
    vi.mocked(axios.post).mockResolvedValue({ data: mockMetrics });

    const { result, rerender } = renderHook(
      ({ enabled }) =>
        useQualityScore({
          content: '<p>Test content</p>',
          siteUrl: 'https://example.com',
          siteId: 1,
          enabled,
        }),
      { initialProps: { enabled: true } },
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
      await Promise.resolve();
    });

    expect(result.current.metrics).toEqual(mockMetrics);

    // Disable fetching
    rerender({ enabled: false });

    expect(result.current.metrics).toBeNull();
    expect(result.current.isLoading).toBe(false);
    expect(result.current.error).toBeNull();
  });

  it('uses correct route and parameters', async () => {
    vi.mocked(axios.post).mockResolvedValue({ data: mockMetrics });

    renderHook(() =>
      useQualityScore({
        content: '<h1>Title</h1><p>Content here</p>',
        siteUrl: 'https://example.com',
        siteId: 42,
      }),
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
      await Promise.resolve();
    });

    expect(axios.post).toHaveBeenCalledWith(
      expect.stringContaining('/content/analyze'),
      {
        content: '<h1>Title</h1><p>Content here</p>',
        site_url: 'https://example.com',
      },
      expect.objectContaining({
        signal: expect.any(AbortSignal),
      }),
    );
  });

  it('handles Axios errors without response data', async () => {
    vi.mocked(axios.post).mockRejectedValue({
      response: {
        data: {},
      },
      isAxiosError: true,
    });

    vi.mocked(axios.isAxiosError).mockReturnValue(true);

    const { result } = renderHook(() =>
      useQualityScore({
        content: '<p>Test content</p>',
        siteUrl: 'https://example.com',
        siteId: 1,
      }),
    );

    await act(async () => {
      vi.advanceTimersByTime(500);
      await Promise.resolve();
    });

    expect(result.current.error).toBe('Failed to analyze content');
  });
});
