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, afterEach } from 'vitest';
import { SharedQuizView } from '../../components/SharedQuizView';
import { MemoryRouter } from 'react-router-dom';

const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
  const actual = await vi.importActual('react-router-dom');
  return {
    ...actual,
    useNavigate: () => mockNavigate,
  };
});

const mockAuthFetch = vi.fn();
const mockSigninRedirect = vi.fn();
vi.mock('../../hooks/useAuthenticatedFetch', () => ({
  useAuthenticatedFetch: () => ({
    authFetch: mockAuthFetch,
    isAuthenticated: true,
  }),
}));

let mockIsAuthenticated = false;
vi.mock('react-oidc-context', () => ({
  useAuth: () => ({
    isAuthenticated: mockIsAuthenticated,
    signinRedirect: mockSigninRedirect,
  }),
}));

vi.mock('react-hot-toast', () => ({
  default: {
    success: vi.fn(),
    error: vi.fn(),
  },
}));

const mockSharedQuiz = {
  title: 'Shared Test Quiz',
  source: 'manual' as const,
  aiTopic: null,
  gameConfig: null,
  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' },
      ],
    },
    {
      id: 'q2',
      text: 'What is the capital of France?',
      timeLimit: 15,
      options: [
        { text: 'London', isCorrect: false, shape: 'triangle', color: 'red' },
        { text: 'Paris', isCorrect: true, shape: 'diamond', color: 'blue' },
      ],
    },
  ],
  questionCount: 2,
};

const renderWithRouter = (token: string = 'valid-token') => {
  const onHostQuiz = vi.fn();
  
  render(
    <MemoryRouter initialEntries={[`/shared/${token}`]}>
      <SharedQuizView onHostQuiz={onHostQuiz} shareToken={token} />
    </MemoryRouter>
  );
  
  return { onHostQuiz };
};

describe('SharedQuizView', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockIsAuthenticated = false;
    global.fetch = vi.fn();
  });

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

  describe('loading state', () => {
    it('shows loading state while fetching quiz', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockImplementation(
        () => new Promise(() => {})
      );

      renderWithRouter();

      expect(screen.getByText('Loading shared quiz...')).toBeInTheDocument();
    });
  });

  describe('error states', () => {
    it('shows error when quiz not found (404)', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: false,
        status: 404,
      });

      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Quiz Not Found')).toBeInTheDocument();
        expect(screen.getByText('This quiz is no longer available')).toBeInTheDocument();
      });
    });

    it('shows generic error for other failures', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: false,
        status: 500,
      });

      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Quiz Not Found')).toBeInTheDocument();
        expect(screen.getByText('Failed to load quiz')).toBeInTheDocument();
      });
    });

    it('shows error on network failure', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockRejectedValueOnce(
        new Error('Network error')
      );

      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Quiz Not Found')).toBeInTheDocument();
      });
    });

    it('navigates home when Go Home clicked on error', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: false,
        status: 404,
      });

      const user = userEvent.setup();
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Go Home')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Go Home'));

      expect(mockNavigate).toHaveBeenCalledWith('/');
    });
  });

  describe('successful quiz load', () => {
    beforeEach(() => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(mockSharedQuiz),
      });
    });

    it('displays quiz title', async () => {
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Shared Test Quiz')).toBeInTheDocument();
      });
    });

    it('displays question count', async () => {
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText(/2 questions/)).toBeInTheDocument();
      });
    });

    it('shows singular for 1 question', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockReset();
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ ...mockSharedQuiz, questionCount: 1, questions: [mockSharedQuiz.questions[0]] }),
      });

      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText(/1 question/)).toBeInTheDocument();
      });
    });

    it('displays AI topic when present', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockReset();
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ ...mockSharedQuiz, source: 'ai_generated', aiTopic: 'Science' }),
      });

      renderWithRouter();

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

    it('shows Host Game button', async () => {
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Host Game')).toBeInTheDocument();
      });
    });

    it('calls onHostQuiz with quiz data when Host Game clicked', async () => {
      const user = userEvent.setup();
      const { onHostQuiz } = renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Host Game')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Host Game'));

      expect(onHostQuiz).toHaveBeenCalledWith({
        title: 'Shared Test Quiz',
        questions: mockSharedQuiz.questions,
        config: undefined,
      });
    });

    it('includes gameConfig in hosted quiz when present', async () => {
      const gameConfig = { shuffleQuestions: true, shuffleAnswers: false };
      (global.fetch as ReturnType<typeof vi.fn>).mockReset();
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ ...mockSharedQuiz, gameConfig }),
      });

      const user = userEvent.setup();
      const { onHostQuiz } = renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Host Game')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Host Game'));

      expect(onHostQuiz).toHaveBeenCalledWith({
        title: 'Shared Test Quiz',
        questions: mockSharedQuiz.questions,
        config: gameConfig,
      });
    });

    it('shows Back to Home link', async () => {
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('← Back to Home')).toBeInTheDocument();
      });
    });

    it('navigates home when Back to Home clicked', async () => {
      const user = userEvent.setup();
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('← Back to Home')).toBeInTheDocument();
      });

      await user.click(screen.getByText('← Back to Home'));

      expect(mockNavigate).toHaveBeenCalledWith('/');
    });
  });

  describe('question preview', () => {
    beforeEach(() => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(mockSharedQuiz),
      });
    });

    it('shows Preview Questions button', async () => {
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Preview Questions')).toBeInTheDocument();
      });
    });

    it('expands questions when Preview Questions clicked', async () => {
      const user = userEvent.setup();
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Preview Questions')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Preview Questions'));

      await waitFor(() => {
        expect(screen.getByText('Hide Questions')).toBeInTheDocument();
        expect(screen.getByText('Question 1')).toBeInTheDocument();
        expect(screen.getByText('What is 2 + 2?')).toBeInTheDocument();
        expect(screen.getByText('Question 2')).toBeInTheDocument();
        expect(screen.getByText('What is the capital of France?')).toBeInTheDocument();
      });
    });

    it('collapses questions when Hide Questions clicked', async () => {
      const user = userEvent.setup();
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Preview Questions')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Preview Questions'));

      await waitFor(() => {
        expect(screen.getByText('Hide Questions')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Hide Questions'));

      await waitFor(() => {
        expect(screen.getByText('Preview Questions')).toBeInTheDocument();
      });
    });
  });

  describe('save to library - unauthenticated user', () => {
    beforeEach(() => {
      mockIsAuthenticated = false;
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(mockSharedQuiz),
      });
    });

    it('shows Sign in to Save button for unauthenticated users', async () => {
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Sign in to Save')).toBeInTheDocument();
      });
    });

    it('triggers sign in when Sign in to Save clicked', async () => {
      const user = userEvent.setup();
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Sign in to Save')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Sign in to Save'));

      expect(mockSigninRedirect).toHaveBeenCalled();
    });
  });

  describe('save to library - authenticated user', () => {
    beforeEach(() => {
      mockIsAuthenticated = true;
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(mockSharedQuiz),
      });
    });

    it('shows Save to My Library button for authenticated users', async () => {
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Save to My Library')).toBeInTheDocument();
      });
    });

    it('calls copy API when Save to My Library clicked', async () => {
      mockAuthFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: 'new-id', title: 'Shared Test Quiz' }),
      });

      const user = userEvent.setup();
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Save to My Library')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Save to My Library'));

      await waitFor(() => {
        expect(mockAuthFetch).toHaveBeenCalledWith('/api/shared/valid-token/copy', {
          method: 'POST',
        });
      });
    });

    it('shows saving state while copying', async () => {
      let resolvePromise: (value: unknown) => void;
      mockAuthFetch.mockReturnValueOnce(new Promise((resolve) => {
        resolvePromise = resolve;
      }));

      const user = userEvent.setup();
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Save to My Library')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Save to My Library'));

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

      resolvePromise!({
        ok: true,
        json: () => Promise.resolve({ id: 'new-id', title: 'Test' }),
      });
    });

    it('shows success toast after saving', async () => {
      const toast = await import('react-hot-toast');
      mockAuthFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: 'new-id', title: 'My Quiz Copy' }),
      });

      const user = userEvent.setup();
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Save to My Library')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Save to My Library'));

      await waitFor(() => {
        expect(toast.default.success).toHaveBeenCalledWith(
          '"My Quiz Copy" saved to your library!'
        );
      });
    });

    it('shows error toast when save fails', async () => {
      const toast = await import('react-hot-toast');
      mockAuthFetch.mockResolvedValueOnce({
        ok: false,
        status: 500,
      });

      const user = userEvent.setup();
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Save to My Library')).toBeInTheDocument();
      });

      await user.click(screen.getByText('Save to My Library'));

      await waitFor(() => {
        expect(toast.default.error).toHaveBeenCalledWith(
          'Failed to save quiz to library'
        );
      });
    });

    it('disables save button while saving', async () => {
      let resolvePromise: (value: unknown) => void;
      mockAuthFetch.mockReturnValueOnce(new Promise((resolve) => {
        resolvePromise = resolve;
      }));

      const user = userEvent.setup();
      renderWithRouter();

      await waitFor(() => {
        expect(screen.getByText('Save to My Library')).toBeInTheDocument();
      });

      const saveButton = screen.getByText('Save to My Library').closest('button')!;
      await user.click(saveButton);

      await waitFor(() => {
        const savingButton = screen.getByText('Saving...').closest('button')!;
        expect(savingButton).toBeDisabled();
      });

      resolvePromise!({
        ok: true,
        json: () => Promise.resolve({ id: 'new-id', title: 'Test' }),
      });
    });
  });

  describe('fetches correct token from URL', () => {
    it('fetches quiz with token from URL', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve(mockSharedQuiz),
      });

      renderWithRouter('my-special-token');

      await waitFor(() => {
        expect(global.fetch).toHaveBeenCalledWith(
          expect.stringContaining('/api/shared/my-special-token')
        );
      });
    });
  });
});
