import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { QuizEditor } from '../../components/QuizEditor';
import { DEFAULT_GAME_CONFIG } from '../../types';
import type { Quiz, GameConfig } from '../../types';

vi.mock('uuid', () => ({
  v4: () => 'mock-uuid-' + Math.random().toString(36).substr(2, 9),
}));

const createMockQuiz = (overrides?: Partial<Quiz>): Quiz => ({
  title: 'Test Quiz',
  questions: [
    {
      id: 'q1',
      text: 'What is 2+2?',
      timeLimit: 20,
      options: [
        { text: '3', isCorrect: false, shape: 'triangle', color: 'red' },
        { text: '4', isCorrect: true, shape: 'diamond', color: 'blue' },
        { text: '5', isCorrect: false, shape: 'circle', color: 'yellow' },
        { text: '6', isCorrect: false, shape: 'square', color: 'green' },
      ],
    },
  ],
  ...overrides,
});

describe('QuizEditor - Async Default Config Loading', () => {
  const mockOnSave = vi.fn();
  const mockOnStartGame = vi.fn();
  const mockOnConfigChange = vi.fn();
  const mockOnBack = vi.fn();

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

  const renderEditor = (props: Partial<React.ComponentProps<typeof QuizEditor>> = {}) => {
    const defaultProps = {
      quiz: createMockQuiz(),
      onSave: mockOnSave,
      onStartGame: mockOnStartGame,
      onConfigChange: mockOnConfigChange,
      onBack: mockOnBack,
      showSaveButton: true,
      isSaving: false,
    };

    return render(<QuizEditor {...defaultProps} {...props} />);
  };

  describe('Initial config state', () => {
    it('should use DEFAULT_GAME_CONFIG when quiz has no config and no defaultConfig prop', async () => {
      const user = userEvent.setup();
      renderEditor();

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: DEFAULT_GAME_CONFIG,
        }),
        DEFAULT_GAME_CONFIG
      );
    });

    it('should use quiz.config when provided, ignoring defaultConfig prop', async () => {
      const user = userEvent.setup();
      const quizConfig: GameConfig = {
        ...DEFAULT_GAME_CONFIG,
        shuffleQuestions: true,
        hostParticipates: false,
      };

      renderEditor({
        quiz: createMockQuiz({ config: quizConfig }),
        defaultConfig: {
          ...DEFAULT_GAME_CONFIG,
          shuffleAnswers: true,
        },
      });

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: expect.objectContaining({
            shuffleQuestions: true,
            hostParticipates: false,
            shuffleAnswers: false,
          }),
        }),
        expect.any(Object)
      );
    });
  });

  describe('Async defaultConfig loading (race condition fix)', () => {
    it('should apply defaultConfig when it loads after initial render', async () => {
      const user = userEvent.setup();

      const userDefaultConfig: GameConfig = {
        ...DEFAULT_GAME_CONFIG,
        shuffleQuestions: true,
        streakBonusEnabled: true,
      };

      const { rerender } = renderEditor({
        defaultConfig: DEFAULT_GAME_CONFIG,
      });

      rerender(
        <QuizEditor
          quiz={createMockQuiz()}
          onSave={mockOnSave}
          onStartGame={mockOnStartGame}
          onConfigChange={mockOnConfigChange}
          onBack={mockOnBack}
          showSaveButton={true}
          defaultConfig={userDefaultConfig}
        />
      );

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: expect.objectContaining({
            shuffleQuestions: true,
            streakBonusEnabled: true,
          }),
        }),
        expect.any(Object)
      );
    });

    it('should NOT override quiz.config when defaultConfig loads later', async () => {
      const user = userEvent.setup();

      const quizConfig: GameConfig = {
        ...DEFAULT_GAME_CONFIG,
        shuffleQuestions: false,
        hostParticipates: false,
      };

      const userDefaultConfig: GameConfig = {
        ...DEFAULT_GAME_CONFIG,
        shuffleQuestions: true,
        hostParticipates: true,
      };

      const { rerender } = renderEditor({
        quiz: createMockQuiz({ config: quizConfig }),
        defaultConfig: DEFAULT_GAME_CONFIG,
      });

      rerender(
        <QuizEditor
          quiz={createMockQuiz({ config: quizConfig })}
          onSave={mockOnSave}
          onStartGame={mockOnStartGame}
          onConfigChange={mockOnConfigChange}
          onBack={mockOnBack}
          showSaveButton={true}
          defaultConfig={userDefaultConfig}
        />
      );

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: expect.objectContaining({
            shuffleQuestions: false,
            hostParticipates: false,
          }),
        }),
        expect.any(Object)
      );
    });

    it('should only apply defaultConfig once (not on every change)', async () => {
      const user = userEvent.setup();

      const userDefaultConfig: GameConfig = {
        ...DEFAULT_GAME_CONFIG,
        shuffleQuestions: true,
      };

      const updatedDefaultConfig: GameConfig = {
        ...DEFAULT_GAME_CONFIG,
        shuffleQuestions: true,
        shuffleAnswers: true,
      };

      const { rerender } = renderEditor({
        defaultConfig: DEFAULT_GAME_CONFIG,
      });

      rerender(
        <QuizEditor
          quiz={createMockQuiz()}
          onSave={mockOnSave}
          onStartGame={mockOnStartGame}
          onConfigChange={mockOnConfigChange}
          onBack={mockOnBack}
          showSaveButton={true}
          defaultConfig={userDefaultConfig}
        />
      );

      rerender(
        <QuizEditor
          quiz={createMockQuiz()}
          onSave={mockOnSave}
          onStartGame={mockOnStartGame}
          onConfigChange={mockOnConfigChange}
          onBack={mockOnBack}
          showSaveButton={true}
          defaultConfig={updatedDefaultConfig}
        />
      );

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: expect.objectContaining({
            shuffleQuestions: true,
            shuffleAnswers: false,
          }),
        }),
        expect.any(Object)
      );
    });

    it('should NOT apply defaultConfig if it equals DEFAULT_GAME_CONFIG', async () => {
      const user = userEvent.setup();

      const { rerender } = renderEditor({
        defaultConfig: undefined,
      });

      rerender(
        <QuizEditor
          quiz={createMockQuiz()}
          onSave={mockOnSave}
          onStartGame={mockOnStartGame}
          onConfigChange={mockOnConfigChange}
          onBack={mockOnBack}
          showSaveButton={true}
          defaultConfig={DEFAULT_GAME_CONFIG}
        />
      );

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: DEFAULT_GAME_CONFIG,
        }),
        DEFAULT_GAME_CONFIG
      );
    });
  });

  describe('User config modifications after async load', () => {
    it('should preserve user modifications after defaultConfig loads', async () => {
      const user = userEvent.setup();

      const userDefaultConfig: GameConfig = {
        ...DEFAULT_GAME_CONFIG,
        shuffleQuestions: true,
      };

      const { rerender } = renderEditor({
        defaultConfig: DEFAULT_GAME_CONFIG,
      });

      await user.click(screen.getByRole('button', { name: /Settings/i }));
      await user.click(screen.getByText('Host Participates'));

      rerender(
        <QuizEditor
          quiz={createMockQuiz()}
          onSave={mockOnSave}
          onStartGame={mockOnStartGame}
          onConfigChange={mockOnConfigChange}
          onBack={mockOnBack}
          showSaveButton={true}
          defaultConfig={userDefaultConfig}
        />
      );

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: expect.objectContaining({
            hostParticipates: false,
          }),
        }),
        expect.any(Object)
      );
    });
  });

  describe('Page refresh simulation', () => {
    it('should correctly initialize config after page refresh with quiz.config', async () => {
      const user = userEvent.setup();

      const savedConfig: GameConfig = {
        ...DEFAULT_GAME_CONFIG,
        shuffleQuestions: true,
        streakBonusEnabled: true,
        hostParticipates: false,
      };

      renderEditor({
        quiz: createMockQuiz({ config: savedConfig }),
        defaultConfig: DEFAULT_GAME_CONFIG,
      });

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: expect.objectContaining({
            shuffleQuestions: true,
            streakBonusEnabled: true,
            hostParticipates: false,
          }),
        }),
        expect.any(Object)
      );
    });

    it('should apply user defaults on refresh when quiz has no config', async () => {
      const user = userEvent.setup();

      const userDefaultConfig: GameConfig = {
        ...DEFAULT_GAME_CONFIG,
        shuffleAnswers: true,
        penaltyForWrongAnswer: true,
      };

      renderEditor({
        quiz: createMockQuiz(),
        defaultConfig: userDefaultConfig,
      });

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: expect.objectContaining({
            shuffleAnswers: true,
            penaltyForWrongAnswer: true,
          }),
        }),
        expect.any(Object)
      );
    });
  });

  describe('Non-happy path scenarios', () => {
    it('should handle undefined defaultConfig prop', async () => {
      const user = userEvent.setup();

      renderEditor({
        defaultConfig: undefined,
      });

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: DEFAULT_GAME_CONFIG,
        }),
        DEFAULT_GAME_CONFIG
      );
    });

    it('should handle rapid defaultConfig changes', async () => {
      const user = userEvent.setup();

      const configs = [
        { ...DEFAULT_GAME_CONFIG, shuffleQuestions: true },
        { ...DEFAULT_GAME_CONFIG, shuffleAnswers: true },
        { ...DEFAULT_GAME_CONFIG, hostParticipates: false },
      ];

      const { rerender } = renderEditor({
        defaultConfig: DEFAULT_GAME_CONFIG,
      });

      for (const config of configs) {
        rerender(
          <QuizEditor
            quiz={createMockQuiz()}
            onSave={mockOnSave}
            onStartGame={mockOnStartGame}
            onConfigChange={mockOnConfigChange}
            onBack={mockOnBack}
            showSaveButton={true}
            defaultConfig={config}
          />
        );
      }

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledTimes(1);
    });

    it('should apply defaultConfig even when component rerenders', async () => {
      const user = userEvent.setup();

      const userDefaultConfig: GameConfig = {
        ...DEFAULT_GAME_CONFIG,
        shuffleQuestions: true,
        hostParticipates: false,
      };

      const { rerender } = renderEditor({
        defaultConfig: DEFAULT_GAME_CONFIG,
      });

      rerender(
        <QuizEditor
          quiz={createMockQuiz()}
          onSave={mockOnSave}
          onStartGame={mockOnStartGame}
          onConfigChange={mockOnConfigChange}
          onBack={mockOnBack}
          showSaveButton={true}
          defaultConfig={userDefaultConfig}
        />
      );

      await user.click(screen.getByText(/Start Game/));

      expect(mockOnStartGame).toHaveBeenCalledWith(
        expect.objectContaining({
          config: expect.objectContaining({
            shuffleQuestions: true,
            hostParticipates: false,
          }),
        }),
        expect.any(Object)
      );
    });
  });
});

describe('QuizEditor - Config Priority Order', () => {
  const mockOnStartGame = vi.fn();

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

  it('should follow priority: quiz.config > defaultConfig > DEFAULT_GAME_CONFIG', async () => {
    const user = userEvent.setup();

    const quizConfig: GameConfig = {
      ...DEFAULT_GAME_CONFIG,
      shuffleQuestions: true,
    };

    const defaultConfig: GameConfig = {
      ...DEFAULT_GAME_CONFIG,
      shuffleAnswers: true,
    };

    render(
      <QuizEditor
        quiz={createMockQuiz({ config: quizConfig })}
        onSave={vi.fn()}
        onStartGame={mockOnStartGame}
        onBack={vi.fn()}
        defaultConfig={defaultConfig}
      />
    );

    await user.click(screen.getByText(/Start Game/));

    const calledConfig = mockOnStartGame.mock.calls[0][1];
    expect(calledConfig.shuffleQuestions).toBe(true);
    expect(calledConfig.shuffleAnswers).toBe(false);
  });
});
