import React from 'react';
import { render, screen, waitFor, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import LogoutButton from '../LogoutButton';
import { AuthProvider } from '../../contexts/AuthContext';
import { authService } from '../../services/authService';

// Mock the auth service
vi.mock('../../services/authService', () => ({
  authService: {
    login: vi.fn(),
    logout: vi.fn(),
    getToken: vi.fn(),
    getCurrentUser: vi.fn(),
    validateToken: vi.fn(),
  },
}));

const MockedAuthService = authService as any;

// Wrapper component for tests
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
  <AuthProvider>{children}</AuthProvider>
);

// Helper to wait for auth initialization
const waitForAuthInit = async () => {
  await waitFor(() => {
    expect(screen.getByRole('button', { name: /sign out/i })).toBeInTheDocument();
  }, { timeout: 5000 });
};

describe('LogoutButton', () => {
  const user = userEvent.setup();

  beforeEach(() => {
    vi.clearAllMocks();
    MockedAuthService.getToken.mockReturnValue('mock-token');
    MockedAuthService.getCurrentUser.mockReturnValue({
      id: '1',
      email: 'test@example.com',
      name: 'Test User',
      role: 'user',
    });
    MockedAuthService.validateToken.mockResolvedValue({
      id: '1',
      email: 'test@example.com',
      name: 'Test User',
      role: 'user',
    });
    MockedAuthService.logout.mockResolvedValue(undefined);
  });

  it('renders logout button with default variant', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LogoutButton />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const button = screen.getByRole('button', { name: /sign out/i });
    expect(button).toBeInTheDocument();
    expect(button).toHaveClass('bg-error-600'); // Button variant styling
  });

  it('renders logout link with link variant', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LogoutButton variant="link" />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const button = screen.getByRole('button', { name: /sign out/i });
    expect(button).toBeInTheDocument();
    expect(button).not.toHaveClass('bg-red-600'); // Link variant styling
  });

  it('shows confirmation dialog when showConfirmation is true', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LogoutButton showConfirmation={true} />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const button = screen.getByRole('button', { name: /sign out/i });
    await act(async () => {
      await user.click(button);
    });

    await waitFor(() => {
      expect(screen.getByText(/confirm logout/i)).toBeInTheDocument();
    });

    expect(screen.getByText(/are you sure you want to sign out/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
  });

  it('does not show confirmation dialog when showConfirmation is false', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LogoutButton showConfirmation={false} />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const button = screen.getByRole('button', { name: /sign out/i });
    await act(async () => {
      await user.click(button);
    });

    // Should not show confirmation dialog, should call logout directly
    await waitFor(() => {
      expect(MockedAuthService.logout).toHaveBeenCalled();
    });
  });

  it('calls logout service when confirmed', async () => {
    // Ensure the mock is properly set up
    MockedAuthService.logout.mockResolvedValue(undefined);

    await act(async () => {
      render(
        <TestWrapper>
          <LogoutButton showConfirmation={true} />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const button = screen.getByRole('button', { name: /sign out/i });
    await act(async () => {
      await user.click(button);
    });

    await waitFor(() => {
      expect(screen.getByText(/confirm logout/i)).toBeInTheDocument();
    });

    // In the confirmation dialog, there's only one "Sign Out" button
    const confirmButton = screen.getByRole('button', { name: /sign out/i });
    await act(async () => {
      await user.click(confirmButton);
    });

    // Wait for the logout to be called
    await waitFor(() => {
      expect(MockedAuthService.logout).toHaveBeenCalled();
    }, { timeout: 3000 });
  });

  it('cancels logout when cancel button is clicked', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LogoutButton showConfirmation={true} />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const button = screen.getByRole('button', { name: /sign out/i });
    await act(async () => {
      await user.click(button);
    });

    await waitFor(() => {
      expect(screen.getByText(/confirm logout/i)).toBeInTheDocument();
    });

    const cancelButton = screen.getByRole('button', { name: /cancel/i });
    await act(async () => {
      await user.click(cancelButton);
    });

    await waitFor(() => {
      expect(screen.queryByText(/confirm logout/i)).not.toBeInTheDocument();
    });

    expect(MockedAuthService.logout).not.toHaveBeenCalled();
  });

  it('shows loading state during logout', async () => {
    // Mock logout to be slow so we can see loading state
    MockedAuthService.logout.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));

    await act(async () => {
      render(
        <TestWrapper>
          <LogoutButton showConfirmation={false} />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const button = screen.getByRole('button', { name: /sign out/i });
    await act(async () => {
      await user.click(button);
    });

    // Should show loading state immediately
    await waitFor(() => {
      expect(screen.getByText(/signing out.../i)).toBeInTheDocument();
    });

    const loadingButton = screen.getByText(/signing out.../i).closest('button');
    expect(loadingButton).toBeDisabled();
  });

  it('handles logout errors gracefully', async () => {
    const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
    MockedAuthService.logout.mockRejectedValue(new Error('Logout failed'));

    await act(async () => {
      render(
        <TestWrapper>
          <LogoutButton showConfirmation={false} />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const button = screen.getByRole('button', { name: /sign out/i });
    await act(async () => {
      await user.click(button);
    });

    // The error is logged in AuthContext, not directly in the component
    await waitFor(() => {
      expect(consoleSpy).toHaveBeenCalledWith('Logout error:', expect.any(Error));
    });

    consoleSpy.mockRestore();
  });

  it('applies custom className', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LogoutButton className="custom-class" />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const button = screen.getByRole('button', { name: /sign out/i });
    expect(button).toHaveClass('custom-class');
  });
});