// @vitest-environment jsdom
import { renderHook, waitFor } from '@testing-library/react';
import nock from 'nock';
import { act } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import createTestProvidersWrapper from '@/__test__/createTestProvidersWrapper';
import { getMockModule, mockApiScope } from '@/__test__/mockApiClient';

import { useImageGenerationPolling } from './useImageGenerationPolling';

vi.mock('@/lib/apiClient', () => getMockModule());

// useImageGenerationForPolling.test.tsx
describe('useImageGenerationPolling', () => {
  describe('startGenerationFromSongWithPolling', () => {
    let textGenerateCalls: ReturnType<typeof vi.fn>;
    let imageGenerateCalls: ReturnType<typeof vi.fn>;

    beforeEach(() => {
      nock.cleanAll();
      // Recreate spy functions for each test
      textGenerateCalls = vi.fn().mockReturnValue(true);
      imageGenerateCalls = vi.fn().mockReturnValue(true);
    });

    it('generates a text prompt and starts image generation', async () => {
      const textGenerateScope = mockApiScope
        .post('/api/video_gen/text/generate', textGenerateCalls)
        .reply(200, () => ({
          id: 'test-generated-text-id',
          prompt: 'generated prompt in the test',
        }));

      const imageGenerateScope = mockApiScope
        .post('/api/video_gen/image/generate', imageGenerateCalls)
        .reply(200, () => ({
          batch_id: 'test-batch-id',
          status: 'processing', // "complete" | "error" | "processing"
        }));

      const { result } = renderHook(() => useImageGenerationPolling(), {
        wrapper: createTestProvidersWrapper(),
      });

      const mutationResult = await act(async () => {
        return await result.current.startGenerationFromSongWithPolling({
          clipId: 'test-clip-id',
          models: ['sdxl-lightning'],
        });
      });

      expect(textGenerateScope.isDone()).toBe(true);
      expect(textGenerateCalls).toHaveBeenCalledWith({
        clip_id: 'test-clip-id',
        target: 'image',
        user_prompt: null,
      });
      expect(imageGenerateScope.isDone()).toBe(true);
      expect(imageGenerateCalls).toHaveBeenCalledWith({
        generated_text_id: 'test-generated-text-id',
        prompt: 'generated prompt in the test',
        quantity: 2,
        models: ['sdxl-lightning'],
      });

      expect(mutationResult).toEqual({ batchId: 'test-batch-id' });
    });

    it('handles failure when text generation fails', async () => {
      mockApiScope
        .post('/api/video_gen/text/generate')
        .reply(500, { error: 'Text generation failed' });

      const { result } = renderHook(() => useImageGenerationPolling(), {
        wrapper: createTestProvidersWrapper(),
      });

      try {
        await act(async () => {
          await result.current.startGenerationFromSongWithPolling({
            clipId: 'test-clip-id',
            models: ['sdxl-lightning'],
          });
        });
        expect.fail('Expected mutation to throw an error');
      } catch (error) {
        expect(error).toBeInstanceOf(Error);
        expect((error as Error).message).toBe('Failed to generate text prompt');
      }
    });

    it('handles failure when image generation fails', async () => {
      mockApiScope.post('/api/video_gen/text/generate').reply(200, () => ({
        id: 'test-generated-text-id',
        prompt: 'generated prompt in the test',
      }));

      mockApiScope
        .post('/api/video_gen/image/generate')
        .reply(500, { error: 'Image generation failed' });

      const { result } = renderHook(() => useImageGenerationPolling(), {
        wrapper: createTestProvidersWrapper(),
      });

      try {
        await act(async () => {
          await result.current.startGenerationFromSongWithPolling({
            clipId: 'test-clip-id',
            models: ['sdxl-lightning'],
          });
        });
        // Should not reach here
        expect.fail('Expected mutation to throw an error');
      } catch (error) {
        expect(error).toBeInstanceOf(Error);
        expect((error as Error).message).toBe(
          'Failed to start image generation'
        );
      }
    });
  });

  describe('polling behavior', () => {
    beforeEach(() => {
      nock.cleanAll();
    });

    it('stops polling when status is complete', async () => {
      // Use a shorter interval for faster tests
      const pollingInterval = 50; // Real 50ms

      mockApiScope
        .post('/api/video_gen/text/generate')
        .reply(200, { id: 'text-id', prompt: 'test prompt' });

      mockApiScope
        .post('/api/video_gen/image/generate')
        .reply(200, { batch_id: 'test-batch', status: 'processing' });

      let pollCount = 0;
      mockApiScope
        .get('/api/video_gen/image/generate/test-batch')
        .times(3)
        .reply(200, () => {
          pollCount++;
          if (pollCount <= 2) {
            return { batch_id: 'test-batch', status: 'processing', images: [] };
          } else {
            return {
              batch_id: 'test-batch',
              status: 'complete',
              images: [
                { id: 'img-1', image_url: 'https://example.com/img.jpg' },
              ],
            };
          }
        });

      const { result } = renderHook(
        () => useImageGenerationPolling({ pollingInterval }),
        { wrapper: createTestProvidersWrapper() }
      );

      // Start generation
      await act(async () => {
        await result.current.startGenerationFromSongWithPolling({
          clipId: 'test-clip',
          models: ['sdxl-lightning'],
        });
      });

      // Wait for polling to start
      await waitFor(() => {
        expect(result.current.isLoading).toBe(true);
      });

      // Wait for completion (polling will happen naturally in real time)
      await waitFor(
        () => {
          expect(result.current.imageUrl).toBe('https://example.com/img.jpg');
        },
        { timeout: 5000, interval: 25 }
      );

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

    it('stops polling after maxRetries', async () => {
      const maxRetries = 3;
      const pollingInterval = 50;

      mockApiScope
        .post('/api/video_gen/text/generate')
        .reply(200, { id: 'text-id', prompt: 'test prompt' });

      mockApiScope
        .post('/api/video_gen/image/generate')
        .reply(200, { batch_id: 'test-batch', status: 'processing' });

      // Always return processing (never completes)
      mockApiScope
        .get('/api/video_gen/image/generate/test-batch')
        .reply(200, {
          batch_id: 'test-batch',
          status: 'processing',
          images: [],
        })
        .persist();

      const { result } = renderHook(
        () => useImageGenerationPolling({ maxRetries, pollingInterval }),
        { wrapper: createTestProvidersWrapper() }
      );

      // Start generation
      await act(async () => {
        await result.current.startGenerationFromSongWithPolling({
          clipId: 'test-clip',
          models: ['sdxl-lightning'],
        });
      });

      // Initially loading
      expect(result.current.isLoading).toBe(true);

      // Wait for timeout (maxRetries * pollingInterval = 150ms + some buffer)
      await waitFor(
        () => {
          expect(result.current.isLoading).toBe(false);
        },
        { timeout: 1000, interval: 25 }
      );

      expect(result.current.error).toBe('Failed to generate');
      expect(result.current.imageUrl).toBeNull();
    });

    it('stops polling when status is error', async () => {
      const pollingInterval = 50;

      mockApiScope
        .post('/api/video_gen/text/generate')
        .reply(200, { id: 'text-id', prompt: 'test prompt' });

      mockApiScope
        .post('/api/video_gen/image/generate')
        .reply(200, { batch_id: 'test-batch', status: 'processing' });

      let pollCount = 0;
      mockApiScope
        .get('/api/video_gen/image/generate/test-batch')
        .times(3) // Allow many calls
        .reply(200, () => {
          pollCount++;

          if (pollCount <= 2) {
            return { batch_id: 'test-batch', status: 'processing', images: [] };
          } else {
            return {
              batch_id: 'test-batch',
              status: 'error',
              images: [],
            };
          }
        });

      const { result } = renderHook(
        () => useImageGenerationPolling({ pollingInterval }),
        { wrapper: createTestProvidersWrapper() }
      );

      // Start generation
      await act(async () => {
        await result.current.startGenerationFromSongWithPolling({
          clipId: 'test-clip',
          models: ['sdxl-lightning'],
        });
      });

      // Wait for error state
      await waitFor(
        () => {
          expect(result.current.error).toBe('Failed to generate');
        },
        { timeout: 1000, interval: 25 }
      );

      // Verify polling stopped
      expect(result.current.isLoading).toBe(false);
      expect(result.current.imageUrl).toBeNull();
    });

    it('cancels old polling when new batchId is set', async () => {
      const pollingInterval = 50;

      // Mock text generation - called twice (once for each generation)
      let textGenCallCount = 0;
      mockApiScope
        .post('/api/video_gen/text/generate')
        .times(2)
        .reply(200, () => {
          textGenCallCount++;
          return {
            id: `text-id-${textGenCallCount}`,
            prompt: `test prompt ${textGenCallCount}`,
          };
        });

      // Mock image generation - called twice
      let imageGenCallCount = 0;
      mockApiScope
        .post('/api/video_gen/image/generate')
        .times(2)
        .reply(200, () => {
          imageGenCallCount++;
          return {
            batch_id: `batch-${imageGenCallCount}`,
            status: 'processing',
          };
        });

      // Mock polling for batch-2 (returns processing then complete)
      let batchPollCount = 0;
      mockApiScope
        .get('/api/video_gen/image/generate/batch-2')
        .times(10)
        .reply(200, () => {
          batchPollCount++;
          if (batchPollCount <= 1) {
            return { batch_id: 'batch-1', status: 'processing', images: [] };
          } else {
            return {
              batch_id: 'batch-2',
              status: 'complete',
              images: [
                { id: 'img-2', image_url: 'https://example.com/img2.jpg' },
              ],
            };
          }
        });

      const { result } = renderHook(
        () => useImageGenerationPolling({ pollingInterval }),
        { wrapper: createTestProvidersWrapper() }
      );

      // Start first generation
      await act(async () => {
        await result.current.startGenerationFromSongWithPolling({
          clipId: 'test-clip-1',
          models: ['sdxl-lightning'],
        });
      });

      // Wait for first batch polling to actually start
      await waitFor(() => {
        expect(result.current.isLoading).toBe(true);
      });

      // Start second generation (should cancel first)
      await act(async () => {
        await result.current.startGenerationFromSongWithPolling({
          clipId: 'test-clip-2',
          models: ['sdxl-lightning'],
        });
      });

      // Wait for second generation to complete
      await waitFor(
        () => {
          expect(result.current.imageUrl).toBe('https://example.com/img2.jpg');
        },
        { timeout: 3000 }
      );

      expect(result.current.isLoading).toBe(false);
    });

    it('does not resume old polling on remount', async () => {
      const pollingInterval = 50;

      mockApiScope
        .post('/api/video_gen/text/generate')
        .reply(200, { id: 'text-id', prompt: 'test prompt' });

      mockApiScope
        .post('/api/video_gen/image/generate')
        .reply(200, { batch_id: 'test-batch', status: 'processing' });

      // Track how many times the polling endpoint is called
      let pollCallCount = 0;
      mockApiScope
        .get('/api/video_gen/image/generate/test-batch')
        .times(10)
        .reply(200, () => {
          pollCallCount++;
          return {
            batch_id: 'test-batch',
            status: 'processing',
            images: [],
          };
        });

      const { result, unmount } = renderHook(
        () => useImageGenerationPolling({ pollingInterval }),
        { wrapper: createTestProvidersWrapper() }
      );

      // Start generation
      await act(async () => {
        await result.current.startGenerationFromSongWithPolling({
          clipId: 'test-clip',
          models: ['sdxl-lightning'],
        });
      });

      // Wait for polling to start
      await waitFor(() => {
        expect(result.current.isLoading).toBe(true);
      });

      // Wait for at least one poll to happen
      await waitFor(() => {
        expect(pollCallCount).toBeGreaterThan(0);
      });

      const callCountBeforeUnmount = pollCallCount;

      unmount();

      // Wait some time (longer than polling interval) to ensure no more polls happen
      await new Promise((resolve) => setTimeout(resolve, pollingInterval * 3));

      // Verify polling stopped after unmount
      expect(pollCallCount).toBe(callCountBeforeUnmount);

      // Remount with a new hook instance
      const { result: newResult } = renderHook(
        () => useImageGenerationPolling({ pollingInterval }),
        { wrapper: createTestProvidersWrapper() }
      );

      // New instance should not be loading (old polling should not resume)
      expect(newResult.current.isLoading).toBe(false);
      expect(newResult.current.imageUrl).toBeNull();
    });
  });
});
