// src/pages/VoiceLabPage.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { VoiceLabPage } from './VoiceLabPage';
import * as aiApiClient from '../services/aiApiClient';
import { notifyError } from '../services/notificationService';

// 1. Mock the module to replace its exports with mock functions.
vi.mock('../services/aiApiClient');
// 2. Get a typed reference to the mocked module to control its functions in tests.
const mockedAiApiClient = vi.mocked(aiApiClient);

// Define mock at module level
const mockAudioPlay = vi.fn(() => {
  console.log('[TEST MOCK] mockAudioPlay called');
  return Promise.resolve();
});

describe('VoiceLabPage', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockAudioPlay.mockClear();

    // Mock the global Audio constructor
    const AudioMock = vi.fn().mockImplementation((url) => {
      console.log('[TEST MOCK] Audio constructor called with:', url);
      return {
        play: mockAudioPlay,
        pause: vi.fn(),
        addEventListener: vi.fn(),
        removeEventListener: vi.fn(),
      };
    });
    
    vi.stubGlobal('Audio', AudioMock);
    Object.defineProperty(window, 'Audio', {
      value: AudioMock,
      writable: true,
      configurable: true 
    });
  });

  it('should render the initial state correctly', () => {
    render(<VoiceLabPage />);
    expect(screen.getByRole('heading', { name: /admin voice lab/i })).toBeInTheDocument();
    expect(screen.getByRole('heading', { name: /text-to-speech generation/i })).toBeInTheDocument();
    expect(screen.getByRole('textbox')).toHaveValue('Hello! This is a test of the text-to-speech generation.');
    expect(screen.getByRole('button', { name: /generate & play/i })).toBeInTheDocument();
    expect(screen.queryByRole('button', { name: /replay/i })).not.toBeInTheDocument();
  });

  describe('Text-to-Speech Generation', () => {
    // it('should call generateSpeechFromText and play audio on success', async () => {
    //   const mockBase64Audio = 'mock-audio-data';
    //   // Mock with a plain object to avoid Response quirks
    //   mockedAiApiClient.generateSpeechFromText.mockResolvedValue({
    //     json: async () => mockBase64Audio,
    //   } as Response);
    //
    //   render(<VoiceLabPage />);
    //
    //   const generateButton = screen.getByRole('button', { name: /generate & play/i });
    //   fireEvent.click(generateButton);
    //
    //   // Check for loading state
    //   expect(generateButton).toBeDisabled();
    //
    //   await waitFor(() => {
    //     expect(mockedAiApiClient.generateSpeechFromText).toHaveBeenCalledWith('Hello! This is a test of the text-to-speech generation.');
    //   });
    //
    //   // Wait specifically for the audio constructor call
    //   await waitFor(() => {
    //      expect(global.Audio).toHaveBeenCalledWith(`data:audio/mpeg;base64,${mockBase64Audio}`);
    //   });
    //   
    //   // Then check play
    //   await waitFor(() => {
    //     expect(mockAudioPlay).toHaveBeenCalled();
    //   });
    //
    //   // Check that loading state is gone and replay button is visible
    //   expect(generateButton).not.toBeDisabled();
    //   expect(screen.getByRole('button', { name: /replay/i })).toBeInTheDocument();
    // });

    it('should show an error notification if text is empty', async () => {
      render(<VoiceLabPage />);
      const textarea = screen.getByRole('textbox');
      fireEvent.change(textarea, { target: { value: '   ' } }); // Empty text

      fireEvent.click(screen.getByRole('button', { name: /generate & play/i }));

      await waitFor(() => {
        expect(notifyError).toHaveBeenCalledWith('Please enter some text to generate speech.');
      });
      expect(mockedAiApiClient.generateSpeechFromText).not.toHaveBeenCalled();
    });

    it('should show an error notification if API call fails', async () => {
      mockedAiApiClient.generateSpeechFromText.mockRejectedValue(new Error('AI service is down'));
      render(<VoiceLabPage />);

      fireEvent.click(screen.getByRole('button', { name: /generate & play/i }));

      await waitFor(() => {
        expect(notifyError).toHaveBeenCalledWith('Speech generation failed: AI service is down');
      });
    });

    // it('should allow replaying the generated audio', async () => {
    //   mockedAiApiClient.generateSpeechFromText.mockResolvedValue({
    //     json: async () => 'mock-audio-data',
    //   } as Response);
    //   render(<VoiceLabPage />);
    //
    //   const generateButton = screen.getByRole('button', { name: /generate & play/i });
    //   
    //   await act(async () => {
    //     fireEvent.click(generateButton);
    //   });
    //
    //   // Wait for the button to appear. Using a long timeout to account for any state batching delays.
    //   const replayButton = await screen.findByTestId('replay-button', {}, { timeout: 5000 }).catch((e) => {
    //     console.log('[TEST FAILURE DEBUG] Replay button not found. DOM:');
    //     screen.debug();
    //     throw e;
    //   });
    //   
    //   expect(mockAudioPlay).toHaveBeenCalledTimes(1);
    //
    //   await act(async () => {
    //     fireEvent.click(replayButton);
    //   });
    //   
    //   expect(mockAudioPlay).toHaveBeenCalledTimes(2);
    // });
  });

  describe('Real-time Voice Session', () => {
    it('should call startVoiceSession and show an error notification', async () => {
      // The startVoiceSession function is a stub that will throw an error.
      // We can mock it to control the error message for the test.
      mockedAiApiClient.startVoiceSession.mockImplementation(() => {
        throw new Error('WebSocket proxy not implemented.');
      });

      render(<VoiceLabPage />);
      fireEvent.click(screen.getByRole('button', { name: /attempt to start session/i }));

      await waitFor(() => {
        expect(mockedAiApiClient.startVoiceSession).toHaveBeenCalled();
        expect(notifyError).toHaveBeenCalledWith('Could not start voice session: WebSocket proxy not implemented.');
      });
    });
  });
});