/**
 * Frontend Integration Tests - Authentication Flow
 *
 * Tests for complete authentication workflows:
 * - Registration flow
 * - Login flow with MFA
 * - Password reset flow
 * - Session management
 * - Protected route access
 *
 * Author: Claude
 */

import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import '@testing-library/jest-dom';
import { BrowserRouter, Routes, Route, useNavigate } from 'react-router-dom';
import userEvent from '@testing-library/user-event';

// Mock API service
const mockApiService = {
  register: jest.fn(),
  login: jest.fn(),
  verifyMfa: jest.fn(),
  requestPasswordReset: jest.fn(),
  resetPassword: jest.fn(),
  refreshToken: jest.fn(),
  logout: jest.fn(),
  getCurrentUser: jest.fn(),
};

// Mock components
const LoginForm = ({ onSuccess, onMfaRequired, loading, error }) => (
  <div data-testid="login-form">
    <input data-testid="email-input" placeholder="Email" type="email" />
    <input data-testid="password-input" placeholder="Password" type="password" />
    <button data-testid="login-button" disabled={loading}>
      {loading ? 'Logging in...' : 'Login'}
    </button>
    <button data-testid="forgot-password-link">Forgot Password?</button>
    {error && <div data-testid="error-message">{error}</div>}
  </div>
);

const RegisterForm = ({ onSuccess, loading, error }) => (
  <div data-testid="register-form">
    <input data-testid="email-input" placeholder="Email" type="email" />
    <input data-testid="password-input" placeholder="Password" type="password" />
    <input data-testid="confirm-password-input" placeholder="Confirm Password" type="password" />
    <input data-testid="business-name-input" placeholder="Business Name" />
    <button data-testid="register-button" disabled={loading}>
      {loading ? 'Registering...' : 'Register'}
    </button>
    {error && <div data-testid="error-message">{error}</div>}
  </div>
);

const MFAVerificationForm = ({ onSuccess, onResendCode, loading, error }) => (
  <div data-testid="mfa-form">
    <div data-testid="mfa-instructions">Enter the 6-digit code sent to your device</div>
    <input data-testid="mfa-code-input" placeholder="6-digit code" maxLength={6} />
    <button data-testid="verify-button" disabled={loading}>
      {loading ? 'Verifying...' : 'Verify Code'}
    </button>
    <button data-testid="resend-code-button">Resend Code</button>
    {error && <div data-testid="error-message">{error}</div>}
  </div>
);

const ForgotPasswordForm = ({ onSuccess, loading, error }) => (
  <div data-testid="forgot-password-form">
    <div data-testid="instructions">Enter your email to receive a reset link</div>
    <input data-testid="email-input" placeholder="Email" type="email" />
    <button data-testid="submit-button" disabled={loading}>
      {loading ? 'Sending...' : 'Send Reset Link'}
    </button>
    {error && <div data-testid="error-message">{error}</div>}
  </div>
);

const ResetPasswordForm = ({ onSuccess, loading, error }) => (
  <div data-testid="reset-password-form">
    <input data-testid="new-password-input" placeholder="New Password" type="password" />
    <input data-testid="confirm-password-input" placeholder="Confirm New Password" type="password" />
    <button data-testid="reset-button" disabled={loading}>
      {loading ? 'Resetting...' : 'Reset Password'}
    </button>
    {error && <div data-testid="error-message">{error}</div>}
  </div>
);

const ProtectedRoute = ({ children, requiredRole = 'user' }) => {
  const [isAuthenticated, setIsAuthenticated] = React.useState(false);
  const [userRole, setUserRole] = React.useState('user');

  React.useEffect(() => {
    // Mock authentication check
    const checkAuth = async () => {
      try {
        const user = await mockApiService.getCurrentUser();
        setIsAuthenticated(true);
        setUserRole(user.role);
      } catch {
        setIsAuthenticated(false);
      }
    };

    checkAuth();
  }, []);

  if (!isAuthenticated) {
    return <div data-testid="redirecting-to-login">Redirecting to login...</div>;
  }

  if (requiredRole !== 'user' && userRole !== requiredRole) {
    return <div data-testid="access-denied">Access Denied</div>;
  }

  return <div data-testid="protected-content">{children}</div>;
};

const Dashboard = () => (
  <div data-testid="dashboard">
    <h1>Welcome to Dashboard</h1>
    <ProtectedRoute>
      <div>Protected Content</div>
    </ProtectedRoute>
  </div>
);

// Test wrapper with router
const TestApp = () => {
  const navigate = useNavigate();

  return (
    <div>
      <Routes>
        <Route path="/login" element={
          <LoginForm
            onSuccess={(data) => {
              if (data.mfaRequired) {
                navigate('/mfa');
              } else {
                navigate('/dashboard');
              }
            }}
            onMfaRequired={() => navigate('/mfa')}
          />
        } />
        <Route path="/register" element={
          <RegisterForm onSuccess={() => navigate('/login')} />
        } />
        <Route path="/mfa" element={
          <MFAVerificationForm onSuccess={() => navigate('/dashboard')} />
        } />
        <Route path="/forgot-password" element={
          <ForgotPasswordForm onSuccess={() => navigate('/login')} />
        } />
        <Route path="/reset-password/:token" element={
          <ResetPasswordForm onSuccess={() => navigate('/login')} />
        } />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/" element={<div data-testid="home-page">Home Page</div>} />
      </Routes>
    </div>
  );
};

describe('Authentication Flow Integration Tests', () => {
  const user = userEvent.setup();

  beforeEach(() => {
    jest.clearAllMocks();
    mockApiService.getCurrentUser.mockResolvedValue({
      id: 1,
      email: 'test@example.com',
      role: 'user'
    });
  });

  describe('Registration Flow', () => {
    test('complete registration flow', async () => {
      render(
        <BrowserRouter>
          <TestApp />
        </BrowserRouter>
      );

      // Navigate to register page
      await act(async () => {
        window.location.assign = jest.fn();
        window.location.href = '/register';
      });

      render(
        <BrowserRouter initialEntries={['/register']}>
          <TestApp />
        </BrowserRouter>
      );

      // Fill registration form
      const emailInput = screen.getByTestId('email-input');
      const passwordInput = screen.getByTestId('password-input');
      const confirmPasswordInput = screen.getByTestId('confirm-password-input');
      const businessNameInput = screen.getByTestId('business-name-input');
      const registerButton = screen.getByTestId('register-button');

      await user.type(emailInput, 'newuser@example.com');
      await user.type(passwordInput, 'SecurePass123!');
      await user.type(confirmPasswordInput, 'SecurePass123!');
      await user.type(businessNameInput, 'Test Business Sdn Bhd');

      // Mock successful registration
      mockApiService.register.mockResolvedValueOnce({
        success: true,
        message: 'Registration successful'
      });

      await user.click(registerButton);

      await waitFor(() => {
        expect(mockApiService.register).toHaveBeenCalledWith({
          email: 'newuser@example.com',
          password: 'SecurePass123!',
          confirmPassword: 'SecurePass123!',
          businessName: 'Test Business Sdn Bhd'
        });
      });

      // Should redirect to login after successful registration
      await waitFor(() => {
        expect(screen.getByTestId('login-form')).toBeInTheDocument();
      });
    });

    test('registration with password mismatch', async () => {
      render(
        <BrowserRouter initialEntries={['/register']}>
          <TestApp />
        </BrowserRouter>
      );

      const emailInput = screen.getByTestId('email-input');
      const passwordInput = screen.getByTestId('password-input');
      const confirmPasswordInput = screen.getByTestId('confirm-password-input');
      const registerButton = screen.getByTestId('register-button');

      await user.type(emailInput, 'newuser@example.com');
      await user.type(passwordInput, 'SecurePass123!');
      await user.type(confirmPasswordInput, 'DifferentPass123!');

      await user.click(registerButton);

      // Should show validation error
      await waitFor(() => {
        expect(screen.getByTestId('error-message')).toBeInTheDocument();
        expect(screen.getByTestId('error-message')).toHaveTextContent('Passwords do not match');
      });
    });

    test('registration with weak password', async () => {
      render(
        <BrowserRouter initialEntries={['/register']}>
          <TestApp />
        </BrowserRouter>
      );

      const emailInput = screen.getByTestId('email-input');
      const passwordInput = screen.getByTestId('password-input');
      const confirmPasswordInput = screen.getByTestId('confirm-password-input');
      const registerButton = screen.getByTestId('register-button');

      await user.type(emailInput, 'newuser@example.com');
      await user.type(passwordInput, 'weak');
      await user.type(confirmPasswordInput, 'weak');

      await user.click(registerButton);

      // Should show validation error
      await waitFor(() => {
        expect(screen.getByTestId('error-message')).toBeInTheDocument();
        expect(screen.getByTestId('error-message')).toHaveTextContent('Password is too weak');
      });
    });
  });

  describe('Login Flow with MFA', () => {
    test('successful login without MFA', async () => {
      render(
        <BrowserRouter initialEntries={['/login']}>
          <TestApp />
        </BrowserRouter>
      );

      const emailInput = screen.getByTestId('email-input');
      const passwordInput = screen.getByTestId('password-input');
      const loginButton = screen.getByTestId('login-button');

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'SecurePass123!');

      // Mock successful login without MFA
      mockApiService.login.mockResolvedValueOnce({
        success: true,
        user: { id: 1, email: 'test@example.com', role: 'user' },
        accessToken: 'mock-token',
        refreshToken: 'mock-refresh-token',
        mfaRequired: false
      });

      await user.click(loginButton);

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

      // Should redirect to dashboard
      await waitFor(() => {
        expect(screen.getByTestId('dashboard')).toBeInTheDocument();
      });
    });

    test('login requiring MFA verification', async () => {
      render(
        <BrowserRouter initialEntries={['/login']}>
          <TestApp />
        </BrowserRouter>
      );

      const emailInput = screen.getByTestId('email-input');
      const passwordInput = screen.getByTestId('password-input');
      const loginButton = screen.getByTestId('login-button');

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'SecurePass123!');

      // Mock login requiring MFA
      mockApiService.login.mockResolvedValueOnce({
        success: true,
        mfaRequired: true,
        tempToken: 'mock-temp-token'
      });

      await user.click(loginButton);

      // Should redirect to MFA verification
      await waitFor(() => {
        expect(screen.getByTestId('mfa-form')).toBeInTheDocument();
      });

      // Complete MFA verification
      const mfaCodeInput = screen.getByTestId('mfa-code-input');
      const verifyButton = screen.getByTestId('verify-button');

      await user.type(mfaCodeInput, '123456');

      mockApiService.verifyMfa.mockResolvedValueOnce({
        success: true,
        user: { id: 1, email: 'test@example.com', role: 'user' },
        accessToken: 'mock-token',
        refreshToken: 'mock-refresh-token'
      });

      await user.click(verifyButton);

      await waitFor(() => {
        expect(mockApiService.verifyMfa).toHaveBeenCalledWith({
          code: '123456',
          tempToken: 'mock-temp-token'
        });
      });

      // Should redirect to dashboard
      await waitFor(() => {
        expect(screen.getByTestId('dashboard')).toBeInTheDocument();
      });
    });

    test('login with invalid credentials', async () => {
      render(
        <BrowserRouter initialEntries={['/login']}>
          <TestApp />
        </BrowserRouter>
      );

      const emailInput = screen.getByTestId('email-input');
      const passwordInput = screen.getByTestId('password-input');
      const loginButton = screen.getByTestId('login-button');

      await user.type(emailInput, 'invalid@example.com');
      await user.type(passwordInput, 'wrongpassword');

      // Mock failed login
      mockApiService.login.mockRejectedValueOnce({
        response: { data: { message: 'Invalid credentials' } }
      });

      await user.click(loginButton);

      await waitFor(() => {
        expect(screen.getByTestId('error-message')).toBeInTheDocument();
        expect(screen.getByTestId('error-message')).toHaveTextContent('Invalid credentials');
      });

      // Should not redirect
      expect(screen.queryByTestId('dashboard')).not.toBeInTheDocument();
    });
  });

  describe('Password Reset Flow', () => {
    test('complete password reset flow', async () => {
      render(
        <BrowserRouter initialEntries={['/login']}>
          <TestApp />
        </BrowserRouter>
      );

      // Navigate to forgot password
      const forgotPasswordLink = screen.getByTestId('forgot-password-link');
      await user.click(forgotPasswordLink);

      await waitFor(() => {
        expect(screen.getByTestId('forgot-password-form')).toBeInTheDocument();
      });

      // Request password reset
      const emailInput = screen.getByTestId('email-input');
      const submitButton = screen.getByTestId('submit-button');

      await user.type(emailInput, 'test@example.com');

      mockApiService.requestPasswordReset.mockResolvedValueOnce({
        success: true,
        message: 'Reset link sent'
      });

      await user.click(submitButton);

      await waitFor(() => {
        expect(mockApiService.requestPasswordReset).toHaveBeenCalledWith({
          email: 'test@example.com'
        });
      });

      // Should show success message and redirect to login
      await waitFor(() => {
        expect(screen.getByTestId('login-form')).toBeInTheDocument();
      });
    });

    test('password reset with new password', async () => {
      render(
        <BrowserRouter initialEntries={['/reset-password/mock-token']}>
          <TestApp />
        </BrowserRouter>
      );

      const newPasswordInput = screen.getByTestId('new-password-input');
      const confirmPasswordInput = screen.getByTestId('confirm-password-input');
      const resetButton = screen.getByTestId('reset-button');

      await user.type(newPasswordInput, 'NewSecurePass123!');
      await user.type(confirmPasswordInput, 'NewSecurePass123!');

      mockApiService.resetPassword.mockResolvedValueOnce({
        success: true,
        message: 'Password reset successful'
      });

      await user.click(resetButton);

      await waitFor(() => {
        expect(mockApiService.resetPassword).toHaveBeenCalledWith({
          token: 'mock-token',
          newPassword: 'NewSecurePass123!',
          confirmPassword: 'NewSecurePass123!'
        });
      });

      // Should redirect to login
      await waitFor(() => {
        expect(screen.getByTestId('login-form')).toBeInTheDocument();
      });
    });
  });

  describe('Session Management', () => {
    test('protected route access when authenticated', async () => {
      render(
        <BrowserRouter>
          <TestApp />
        </BrowserRouter>
      );

      // Navigate to dashboard
      await act(async () => {
        window.location.href = '/dashboard';
      });

      render(
        <BrowserRouter initialEntries={['/dashboard']}>
          <TestApp />
        </BrowserRouter>
      );

      // Should show protected content when authenticated
      await waitFor(() => {
        expect(screen.getByTestId('protected-content')).toBeInTheDocument();
        expect(screen.getByTestId('dashboard')).toBeInTheDocument();
      });
    });

    test('protected route redirect when not authenticated', async () => {
      // Mock unauthenticated state
      mockApiService.getCurrentUser.mockRejectedValueOnce(new Error('Not authenticated'));

      render(
        <BrowserRouter initialEntries={['/dashboard']}>
          <TestApp />
        </BrowserRouter>
      );

      // Should redirect to login
      await waitFor(() => {
        expect(screen.getByTestId('redirecting-to-login')).toBeInTheDocument();
      });
    });

    test('token refresh on expiration', async () => {
      render(
        <BrowserRouter initialEntries={['/login']}>
          <TestApp />
        </BrowserRouter>
      );

      const emailInput = screen.getByTestId('email-input');
      const passwordInput = screen.getByTestId('password-input');
      const loginButton = screen.getByTestId('login-button');

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'SecurePass123!');

      // Mock successful login
      mockApiService.login.mockResolvedValueOnce({
        success: true,
        user: { id: 1, email: 'test@example.com', role: 'user' },
        accessToken: 'mock-token',
        refreshToken: 'mock-refresh-token',
        mfaRequired: false
      });

      await user.click(loginButton);

      await waitFor(() => {
        expect(screen.getByTestId('dashboard')).toBeInTheDocument();
      });

      // Mock token refresh
      mockApiService.refreshToken.mockResolvedValueOnce({
        success: true,
        accessToken: 'new-mock-token',
        refreshToken: 'new-mock-refresh-token'
      });

      // Simulate token refresh (this would happen automatically in real app)
      await act(async () => {
        await mockApiService.refreshToken();
      });

      expect(mockApiService.refreshToken).toHaveBeenCalledWith({
        refreshToken: 'mock-refresh-token'
      });
    });

    test('logout functionality', async () => {
      render(
        <BrowserRouter initialEntries={['/dashboard']}>
          <TestApp />
        </BrowserRouter>
      );

      await waitFor(() => {
        expect(screen.getByTestId('dashboard')).toBeInTheDocument();
      });

      // Mock logout
      mockApiService.logout.mockResolvedValueOnce({ success: true });

      // Simulate logout action
      await act(async () => {
        await mockApiService.logout();
      });

      expect(mockApiService.logout).toHaveBeenCalled();

      // Should redirect to login after logout
      await waitFor(() => {
        expect(screen.getByTestId('login-form')).toBeInTheDocument();
      });
    });
  });

  describe('Error Handling and Edge Cases', () => {
    test('network error during login', async () => {
      render(
        <BrowserRouter initialEntries={['/login']}>
          <TestApp />
        </BrowserRouter>
      );

      const emailInput = screen.getByTestId('email-input');
      const passwordInput = screen.getByTestId('password-input');
      const loginButton = screen.getByTestId('login-button');

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'SecurePass123!');

      // Mock network error
      mockApiService.login.mockRejectedValueOnce(new Error('Network error'));

      await user.click(loginButton);

      await waitFor(() => {
        expect(screen.getByTestId('error-message')).toBeInTheDocument();
        expect(screen.getByTestId('error-message')).toHaveTextContent('Network error');
      });
    });

    test('MFA code expiration', async () => {
      render(
        <BrowserRouter initialEntries={['/mfa']}>
          <TestApp />
        </BrowserRouter>
      );

      const mfaCodeInput = screen.getByTestId('mfa-code-input');
      const verifyButton = screen.getByTestId('verify-button');

      await user.type(mfaCodeInput, '123456');

      // Mock expired code error
      mockApiService.verifyMfa.mockRejectedValueOnce({
        response: { data: { message: 'Code expired' } }
      });

      await user.click(verifyButton);

      await waitFor(() => {
        expect(screen.getByTestId('error-message')).toBeInTheDocument();
        expect(screen.getByTestId('error-message')).toHaveTextContent('Code expired');
      });
    });

    test('invalid reset password token', async () => {
      render(
        <BrowserRouter initialEntries={['/reset-password/invalid-token']}>
          <TestApp />
        </BrowserRouter>
      );

      const newPasswordInput = screen.getByTestId('new-password-input');
      const confirmPasswordInput = screen.getByTestId('confirm-password-input');
      const resetButton = screen.getByTestId('reset-button');

      await user.type(newPasswordInput, 'NewSecurePass123!');
      await user.type(confirmPasswordInput, 'NewSecurePass123!');

      // Mock invalid token error
      mockApiService.resetPassword.mockRejectedValueOnce({
        response: { data: { message: 'Invalid or expired token' } }
      });

      await user.click(resetButton);

      await waitFor(() => {
        expect(screen.getByTestId('error-message')).toBeInTheDocument();
        expect(screen.getByTestId('error-message')).toHaveTextContent('Invalid or expired token');
      });
    });
  });

  describe('Cross-Browser Compatibility', () => {
    test('handles different browsers', () => {
      // Mock different browser environments
      const mockUserAgent = (agent) => {
        Object.defineProperty(navigator, 'userAgent', {
          writable: true,
          value: agent
        });
      };

      const browsers = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
      ];

      browsers.forEach(agent => {
        mockUserAgent(agent);
        render(
          <BrowserRouter initialEntries={['/login']}>
            <TestApp />
          </BrowserRouter>
        );

        expect(screen.getByTestId('login-form')).toBeInTheDocument();
      });
    });
  });
});