// src/components/RecipeSuggester.test.tsx
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { RecipeSuggester } from './RecipeSuggester'; // This should be after mocks
import * as apiClient from '../services/apiClient';
import { logger } from '../services/logger.client';
import { renderWithProviders } from '../tests/utils/renderWithProviders';
import '@testing-library/jest-dom';

// Must explicitly call vi.mock() for apiClient
vi.mock('../services/apiClient');

const mockedApiClient = vi.mocked(apiClient);

describe('RecipeSuggester Component', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Reset console logs if needed, or just keep them for debug visibility
  });

  it('renders correctly with initial state', () => {
    console.log('TEST: Verifying initial render state');
    renderWithProviders(<RecipeSuggester />);

    expect(screen.getByText('Get a Recipe Suggestion')).toBeInTheDocument();
    expect(screen.getByLabelText(/Ingredients:/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Suggest a Recipe/i })).toBeInTheDocument();
    expect(screen.queryByText('Getting suggestion...')).not.toBeInTheDocument();
  });

  it('shows validation error if no ingredients are entered', async () => {
    console.log('TEST: Verifying validation for empty input');
    const user = userEvent.setup();
    renderWithProviders(<RecipeSuggester />);

    const button = screen.getByRole('button', { name: /Suggest a Recipe/i });
    await user.click(button);

    expect(await screen.findByText('Please enter at least one ingredient.')).toBeInTheDocument();
    expect(mockedApiClient.suggestRecipe).not.toHaveBeenCalled();
    console.log('TEST: Validation error displayed correctly');
  });

  it('calls suggestRecipe and displays suggestion on success', async () => {
    console.log('TEST: Verifying successful recipe suggestion flow');
    const user = userEvent.setup();
    renderWithProviders(<RecipeSuggester />);

    const input = screen.getByLabelText(/Ingredients:/i);
    await user.type(input, 'chicken, rice');

    // Mock successful API response
    const mockSuggestion = 'Here is a nice Chicken and Rice recipe...';
    // Add a delay to ensure the loading state is visible during the test
    mockedApiClient.suggestRecipe.mockImplementation(async () => {
      await new Promise((resolve) => setTimeout(resolve, 50));
      return {
        ok: true,
        json: async () => ({ suggestion: mockSuggestion }),
      } as Response;
    });

    const button = screen.getByRole('button', { name: /Suggest a Recipe/i });
    await user.click(button);

    // Check loading state
    expect(screen.getByRole('button')).toBeDisabled();
    expect(screen.getByText('Getting suggestion...')).toBeInTheDocument();

    await waitFor(() => {
      expect(screen.getByText(mockSuggestion)).toBeInTheDocument();
    });

    expect(mockedApiClient.suggestRecipe).toHaveBeenCalledWith(['chicken', 'rice']);
    console.log('TEST: Suggestion displayed and API called with correct args');
  });

  it('handles API errors (non-200 response) gracefully', async () => {
    console.log('TEST: Verifying API error handling (400/500 responses)');
    const user = userEvent.setup();
    renderWithProviders(<RecipeSuggester />);

    const input = screen.getByLabelText(/Ingredients:/i);
    await user.type(input, 'rocks');

    // Mock API failure response
    const errorMessage = 'Invalid ingredients provided.';
    mockedApiClient.suggestRecipe.mockResolvedValue({
      ok: false,
      json: async () => ({ message: errorMessage }),
    } as Response);

    const button = screen.getByRole('button', { name: /Suggest a Recipe/i });
    await user.click(button);

    await waitFor(() => {
      expect(screen.getByText(errorMessage)).toBeInTheDocument();
    });

    // Ensure loading state is reset
    expect(screen.getByRole('button', { name: /Suggest a Recipe/i })).toBeEnabled();
    console.log('TEST: API error message displayed to user');
  });

  it('handles network exceptions and logs them', async () => {
    console.log('TEST: Verifying network exception handling');
    const user = userEvent.setup();
    renderWithProviders(<RecipeSuggester />);

    const input = screen.getByLabelText(/Ingredients:/i);
    await user.type(input, 'beef');

    // Mock network error
    const networkError = new Error('Network Error');
    mockedApiClient.suggestRecipe.mockRejectedValue(networkError);

    const button = screen.getByRole('button', { name: /Suggest a Recipe/i });
    await user.click(button);

    await waitFor(() => {
      expect(screen.getByText('Network Error')).toBeInTheDocument();
    });

    expect(logger.error).toHaveBeenCalledWith(
      { error: networkError },
      'Failed to fetch recipe suggestion.',
    );
    console.log('TEST: Network error caught and logged');
  });

  it('clears previous errors when submitting again', async () => {
    console.log('TEST: Verifying error clearing on re-submit');
    const user = userEvent.setup();
    renderWithProviders(<RecipeSuggester />);

    // Trigger validation error first
    const button = screen.getByRole('button', { name: /Suggest a Recipe/i });
    await user.click(button);
    expect(screen.getByText('Please enter at least one ingredient.')).toBeInTheDocument();

    // Now type something to clear it (state change doesn't clear it, submit does)
    const input = screen.getByLabelText(/Ingredients:/i);
    await user.type(input, 'tofu');

    // Mock success for the second click
    mockedApiClient.suggestRecipe.mockResolvedValue({
      ok: true,
      json: async () => ({ suggestion: 'Tofu Stir Fry' }),
    } as Response);

    await user.click(button);

    await waitFor(() => {
      expect(screen.queryByText('Please enter at least one ingredient.')).not.toBeInTheDocument();
      expect(screen.getByText('Tofu Stir Fry')).toBeInTheDocument();
    });
    console.log('TEST: Previous error cleared successfully');
  });

  it('uses default error message when API error response has no message', async () => {
    console.log('TEST: Verifying default error message for API failure');
    const user = userEvent.setup();
    renderWithProviders(<RecipeSuggester />);

    const input = screen.getByLabelText(/Ingredients:/i);
    await user.type(input, 'mystery');

    // Mock API failure response without a message property
    mockedApiClient.suggestRecipe.mockResolvedValue({
      ok: false,
      json: async () => ({}), // Empty object
    } as Response);

    const button = screen.getByRole('button', { name: /Suggest a Recipe/i });
    await user.click(button);

    await waitFor(() => {
      expect(screen.getByText('Failed to get suggestion.')).toBeInTheDocument();
    });
  });

  it('handles non-Error objects thrown during fetch', async () => {
    console.log('TEST: Verifying handling of non-Error exceptions');
    const user = userEvent.setup();
    renderWithProviders(<RecipeSuggester />);

    const input = screen.getByLabelText(/Ingredients:/i);
    await user.type(input, 'chaos');

    // Mock a rejection that is NOT an Error object
    mockedApiClient.suggestRecipe.mockRejectedValue('Something weird happened');

    const button = screen.getByRole('button', { name: /Suggest a Recipe/i });
    await user.click(button);

    await waitFor(() => {
      expect(screen.getByText('An unknown error occurred.')).toBeInTheDocument();
    });

    expect(logger.error).toHaveBeenCalledWith(
      { error: 'Something weird happened' },
      'Failed to fetch recipe suggestion.',
    );
  });
});
