import React from 'react';
import { render, screen, waitFor, fireEvent, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import LoginForm from '../LoginForm';
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.getByLabelText(/email address/i)).toBeInTheDocument();
  }, { timeout: 5000 });
};

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

  beforeEach(() => {
    vi.clearAllMocks();
    // Set up default mocks to prevent async initialization issues
    MockedAuthService.getToken.mockReturnValue(null);
    MockedAuthService.getCurrentUser.mockReturnValue(null);
    MockedAuthService.validateToken.mockResolvedValue(null);
  });

  it('renders login form with all required fields', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LoginForm />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    expect(screen.getByLabelText(/email address/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /sign in/i })).toBeInTheDocument();
  });

  it('shows validation errors for empty fields', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LoginForm />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const form = screen.getByRole('button', { name: /sign in/i }).closest('form');
    
    await act(async () => {
      fireEvent.submit(form!);
    });

    await waitFor(() => {
      expect(screen.getByText(/email is required/i)).toBeInTheDocument();
    });
    
    expect(screen.getByText(/password is required/i)).toBeInTheDocument();
  });

  it('shows validation error for invalid email format', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LoginForm />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const emailInput = screen.getByLabelText(/email address/i);
    const passwordInput = screen.getByLabelText(/password/i);
    const form = screen.getByRole('button', { name: /sign in/i }).closest('form');

    await act(async () => {
      await user.type(emailInput, 'invalid-email');
      await user.type(passwordInput, 'password123');
    });
    
    await act(async () => {
      fireEvent.submit(form!);
    });

    await waitFor(() => {
      expect(screen.getByText(/please enter a valid email address/i)).toBeInTheDocument();
    });
  });

  it('shows validation error for short password', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LoginForm />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const emailInput = screen.getByLabelText(/email address/i);
    const passwordInput = screen.getByLabelText(/password/i);
    const form = screen.getByRole('button', { name: /sign in/i }).closest('form');

    await act(async () => {
      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, '123');
    });
    
    await act(async () => {
      fireEvent.submit(form!);
    });

    await waitFor(() => {
      expect(screen.getByText(/password must be at least 6 characters long/i)).toBeInTheDocument();
    });
  });

  it('toggles password visibility', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LoginForm />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const passwordInput = screen.getByLabelText(/password/i) as HTMLInputElement;
    const toggleButtons = screen.getAllByRole('button');
    const toggleButton = toggleButtons.find(button => button.getAttribute('type') === 'button' && !button.textContent?.includes('Sign'));

    expect(passwordInput.type).toBe('password');

    if (toggleButton) {
      await act(async () => {
        await user.click(toggleButton);
      });
      expect(passwordInput.type).toBe('text');

      await act(async () => {
        await user.click(toggleButton);
      });
      expect(passwordInput.type).toBe('password');
    }
  });

  it('clears field errors when user starts typing', async () => {
    await act(async () => {
      render(
        <TestWrapper>
          <LoginForm />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const emailInput = screen.getByLabelText(/email address/i);
    const form = screen.getByRole('button', { name: /sign in/i }).closest('form');

    // Trigger validation error
    await act(async () => {
      fireEvent.submit(form!);
    });
    
    await waitFor(() => {
      expect(screen.getByText(/email is required/i)).toBeInTheDocument();
    });

    // Start typing to clear error
    await act(async () => {
      await user.type(emailInput, 'test@example.com');
    });
    
    await waitFor(() => {
      expect(screen.queryByText(/email is required/i)).not.toBeInTheDocument();
    });
  });

  it('calls login service with correct credentials', async () => {
    const mockAuthResult = {
      user: { id: '1', email: 'test@example.com', name: 'Test User', role: 'user' as const, createdAt: '2023-01-01', updatedAt: '2023-01-01' },
      token: 'mock-token',
      refreshToken: 'mock-refresh-token',
    };

    MockedAuthService.login.mockResolvedValue(mockAuthResult);

    await act(async () => {
      render(
        <TestWrapper>
          <LoginForm />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const emailInput = screen.getByLabelText(/email address/i);
    const passwordInput = screen.getByLabelText(/password/i);
    const form = screen.getByRole('button', { name: /sign in/i }).closest('form');

    await act(async () => {
      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'password123');
    });
    
    await act(async () => {
      fireEvent.submit(form!);
    });

    await waitFor(() => {
      expect(MockedAuthService.login).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123',
      });
    });
  });

  it('shows loading state during login', async () => {
    MockedAuthService.login.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));

    await act(async () => {
      render(
        <TestWrapper>
          <LoginForm />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const emailInput = screen.getByLabelText(/email address/i);
    const passwordInput = screen.getByLabelText(/password/i);
    const submitButton = screen.getByRole('button', { name: /sign in/i });

    await act(async () => {
      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);
    });

    expect(screen.getByText(/signing in.../i)).toBeInTheDocument();
    expect(submitButton).toBeDisabled();
  });

  it('shows error message when login fails', async () => {
    MockedAuthService.login.mockRejectedValue(new Error('Invalid credentials'));

    await act(async () => {
      render(
        <TestWrapper>
          <LoginForm />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const emailInput = screen.getByLabelText(/email address/i);
    const passwordInput = screen.getByLabelText(/password/i);
    const form = screen.getByRole('button', { name: /sign in/i }).closest('form');

    await act(async () => {
      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'wrongpassword');
    });
    
    await act(async () => {
      fireEvent.submit(form!);
    });

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

  it('calls onSuccess callback when login succeeds', async () => {
    const mockOnSuccess = vi.fn();
    const mockAuthResult = {
      user: { id: '1', email: 'test@example.com', name: 'Test User', role: 'user' as const, createdAt: '2023-01-01', updatedAt: '2023-01-01' },
      token: 'mock-token',
      refreshToken: 'mock-refresh-token',
    };

    MockedAuthService.login.mockResolvedValue(mockAuthResult);

    await act(async () => {
      render(
        <TestWrapper>
          <LoginForm onSuccess={mockOnSuccess} />
        </TestWrapper>
      );
    });

    await waitForAuthInit();

    const emailInput = screen.getByLabelText(/email address/i);
    const passwordInput = screen.getByLabelText(/password/i);
    const form = screen.getByRole('button', { name: /sign in/i }).closest('form');

    await act(async () => {
      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'password123');
    });
    
    await act(async () => {
      fireEvent.submit(form!);
    });

    await waitFor(() => {
      expect(mockOnSuccess).toHaveBeenCalled();
    });
  });
});