import { describe, it, expect, vi, beforeEach, beforeAll } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { BrowserRouter } from 'react-router-dom';
import { Login } from './Login';
import * as authApi from '@/services/api/auth';
import { TokenStorage } from '@/services/tokenStorage';
import * as useToastHook from '@/hooks/useToast';

// Mock ResizeObserver for Radix UI components
beforeAll(() => {
  global.ResizeObserver = class ResizeObserver {
    observe() {}
    unobserve() {}
    disconnect() {}
  };
});

// Mock dependencies
vi.mock('@/services/api/auth');
vi.mock('@/services/tokenStorage', () => ({
  TokenStorage: {
    setTokens: vi.fn(),
  },
}));
vi.mock('@/hooks/useToast');

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

const mockLogin = vi.mocked(authApi.login);
const mockSetTokens = vi.mocked(TokenStorage.setTokens);
const mockSuccess = vi.fn();
const mockErrorToast = vi.fn();

const TestWrapper = ({ children }: { children: React.ReactNode }) => (
  <BrowserRouter>{children}</BrowserRouter>
);

describe('Login', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockNavigate.mockClear();
    vi.mocked(useToastHook.useToast).mockReturnValue({
      success: mockSuccess,
      error: mockErrorToast,
    });
  });

  it('should render login form', () => {
    render(
      <TestWrapper>
        <Login />
      </TestWrapper>,
    );

    expect(screen.getByText(/sign in/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/remember me/i)).toBeInTheDocument();
  });

  it('should handle successful login', async () => {
    const user = userEvent.setup();

    mockLogin.mockResolvedValue({
      user: { id: 1, email: 'test@example.com' },
      token: {
        access_token: 'access-token',
        refresh_token: 'refresh-token',
        expires_in: 900,
      },
    });

    render(
      <TestWrapper>
        <Login />
      </TestWrapper>,
    );

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

    await user.type(emailInput, 'test@example.com');
    await user.type(passwordInput, 'password123');
    await user.click(submitButton);

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

    expect(mockSetTokens).toHaveBeenCalledWith('access-token', 'refresh-token');
    expect(mockSuccess).toHaveBeenCalledWith('Login successful! Welcome back.');
    expect(mockNavigate).toHaveBeenCalledWith('/dashboard');
  });

  // T0178: Test pour affichage message session expirée
  it('should display session expired message from sessionStorage', () => {
    const expiredMessage = 'Your session has expired. Please log in again.';
    sessionStorage.setItem('auth_error', expiredMessage);

    render(
      <TestWrapper>
        <Login />
      </TestWrapper>,
    );

    expect(screen.getByText(expiredMessage)).toBeInTheDocument();
    expect(sessionStorage.getItem('auth_error')).toBeNull(); // Should be cleared after reading
  });

  describe('T0170: Error Handling', () => {
    it('should display error message for invalid credentials (401)', async () => {
      const user = userEvent.setup();
      const apiError = {
        message: 'Invalid credentials',
        code: '401',
      };
      mockLogin.mockRejectedValue(apiError);

      render(
        <TestWrapper>
          <Login />
        </TestWrapper>,
      );

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

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'wrongpassword');
      await user.click(submitButton);

      await waitFor(() => {
        expect(
          screen.getByText(/invalid email or password/i),
        ).toBeInTheDocument();
      });

      expect(mockErrorToast).toHaveBeenCalledWith(
        'Invalid email or password. Please check your credentials and try again.',
      );
    });

    it('should display error message for invalid credentials (403)', async () => {
      const user = userEvent.setup();
      const apiError = {
        message: 'Forbidden',
        code: '403',
      };
      mockLogin.mockRejectedValue(apiError);

      render(
        <TestWrapper>
          <Login />
        </TestWrapper>,
      );

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

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      await waitFor(() => {
        expect(
          screen.getByText(/invalid email or password/i),
        ).toBeInTheDocument();
      });
    });

    it('should display error message for network error', async () => {
      const user = userEvent.setup();
      const networkError = {
        message: 'Network error',
        code: 'NETWORK_ERROR',
      };
      mockLogin.mockRejectedValue(networkError);

      render(
        <TestWrapper>
          <Login />
        </TestWrapper>,
      );

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

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      await waitFor(() => {
        expect(
          screen.getByText(/unable to connect to the server/i),
        ).toBeInTheDocument();
      });

      expect(mockErrorToast).toHaveBeenCalledWith(
        'Unable to connect to the server. Please check your internet connection and try again.',
      );
    });

    it('should display error message for server error (500)', async () => {
      const user = userEvent.setup();
      const serverError = {
        message: 'Internal server error',
        code: '500',
      };
      mockLogin.mockRejectedValue(serverError);

      render(
        <TestWrapper>
          <Login />
        </TestWrapper>,
      );

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

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

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

      expect(mockErrorToast).toHaveBeenCalledWith(
        'Server error. Please try again later.',
      );
    });

    it('should display error message for rate limiting (429)', async () => {
      const user = userEvent.setup();
      const rateLimitError = {
        message: 'Too many requests',
        code: '429',
      };
      mockLogin.mockRejectedValue(rateLimitError);

      render(
        <TestWrapper>
          <Login />
        </TestWrapper>,
      );

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

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      await waitFor(() => {
        expect(
          screen.getByText(/too many login attempts/i),
        ).toBeInTheDocument();
      });

      expect(mockErrorToast).toHaveBeenCalledWith(
        'Too many login attempts. Please wait a moment and try again.',
      );
    });

    it('should display error message for invalid request (400)', async () => {
      const user = userEvent.setup();
      const badRequestError = {
        message: 'Invalid request',
        code: '400',
      };
      mockLogin.mockRejectedValue(badRequestError);

      render(
        <TestWrapper>
          <Login />
        </TestWrapper>,
      );

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

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

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

    it('should display generic error message for unknown errors', async () => {
      const user = userEvent.setup();
      const unknownError = {
        message: 'Something went wrong',
        code: 'UNKNOWN_ERROR',
      };
      mockLogin.mockRejectedValue(unknownError);

      render(
        <TestWrapper>
          <Login />
        </TestWrapper>,
      );

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

      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'password123');
      await user.click(submitButton);

      await waitFor(() => {
        expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();
      });
    });

    it('should clear error message on new submission attempt', async () => {
      const user = userEvent.setup();
      const apiError = {
        message: 'Invalid credentials',
        code: '401',
      };

      mockLogin.mockRejectedValueOnce(apiError);
      mockLogin.mockResolvedValueOnce({
        user: { id: 1, email: 'test@example.com' },
        token: {
          access_token: 'access-token',
          refresh_token: 'refresh-token',
          expires_in: 900,
        },
      });

      render(
        <TestWrapper>
          <Login />
        </TestWrapper>,
      );

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

      // First attempt - fails
      await user.type(emailInput, 'test@example.com');
      await user.type(passwordInput, 'wrongpassword');
      await user.click(submitButton);

      await waitFor(() => {
        expect(
          screen.getByText(/invalid email or password/i),
        ).toBeInTheDocument();
      });

      // Second attempt - succeeds
      await user.clear(passwordInput);
      await user.type(passwordInput, 'correctpassword');
      await user.click(submitButton);

      await waitFor(() => {
        expect(
          screen.queryByText(/invalid email or password/i),
        ).not.toBeInTheDocument();
      });
    });
  });
});
