import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
  render,
  screen,
  waitFor,
  fireEvent,
  act,
} from '@testing-library/react';
import { SessionsPage } from './SessionsPage';
import { apiClient } from '@/services/api/client';

// Mock apiClient
vi.mock('@/services/api/client', () => ({
  apiClient: {
    get: vi.fn(),
    delete: vi.fn(),
  },
}));

// Mock window.confirm
const mockConfirm = vi.fn();
window.confirm = mockConfirm;

describe('SessionsPage', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockConfirm.mockReturnValue(true);
  });

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

  const mockSessions = [
    {
      id: '1',
      ip_address: '192.168.1.1',
      user_agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
      last_activity: '2024-01-15T10:30:00Z',
      created_at: '2024-01-10T08:00:00Z',
      is_current: true,
    },
    {
      id: '2',
      ip_address: '192.168.1.2',
      user_agent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0)',
      last_activity: '2024-01-15T09:00:00Z',
      created_at: '2024-01-12T12:00:00Z',
      is_current: false,
    },
    {
      id: '3',
      ip_address: '192.168.1.3',
      user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
      last_activity: '2024-01-14T15:00:00Z',
      created_at: '2024-01-08T10:00:00Z',
      is_current: false,
    },
  ];

  // Test 1: Affiche les sessions correctement
  it('should display sessions correctly', async () => {
    vi.mocked(apiClient.get).mockResolvedValue({
      data: {
        sessions: mockSessions,
        count: 3,
      },
    });

    render(<SessionsPage />);

    await waitFor(() => {
      expect(screen.getByText('Active Sessions')).toBeInTheDocument();
      expect(screen.getByText('Sessions (3)')).toBeInTheDocument();
    });

    expect(screen.getByText('192.168.1.1')).toBeInTheDocument();
    expect(screen.getByText('192.168.1.2')).toBeInTheDocument();
    expect(screen.getByText('192.168.1.3')).toBeInTheDocument();
    expect(screen.getByText('Current Session')).toBeInTheDocument();
  });

  // Test 2: Affiche le loading spinner pendant le chargement
  it('should display loading spinner while fetching sessions', () => {
    vi.mocked(apiClient.get).mockImplementation(
      () => new Promise(() => {}), // Never resolves
    );

    render(<SessionsPage />);

    // Le spinner devrait être visible (on vérifie que le texte principal n'est pas encore là)
    expect(screen.queryByText('Active Sessions')).not.toBeInTheDocument();
  });

  // Test 3: Affiche un message si aucune session n'est trouvée
  it('should display message when no sessions found', async () => {
    vi.mocked(apiClient.get).mockResolvedValue({
      data: {
        sessions: [],
        count: 0,
      },
    });

    render(<SessionsPage />);

    await waitFor(() => {
      expect(screen.getByText('No active sessions found.')).toBeInTheDocument();
    });
  });

  // Test 4: Révoque une session spécifique
  it('should revoke a specific session', async () => {
    vi.mocked(apiClient.get).mockResolvedValue({
      data: {
        sessions: mockSessions,
        count: 3,
      },
    });
    vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });

    render(<SessionsPage />);

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

    const revokeButtons = screen.getAllByRole('button', { name: /revoke/i });
    // Filter out the "Revoke All Other Sessions" button
    const sessionRevokeButtons = revokeButtons.filter(
      (btn) => !btn.textContent?.includes('All Other'),
    );

    await act(async () => {
      fireEvent.click(sessionRevokeButtons[0]);
    });

    // Wait for confirmation dialog to appear and confirm
    await waitFor(
      () => {
        expect(
          screen.getByText(/are you sure you want to revoke this session/i),
        ).toBeInTheDocument();
      },
      { timeout: 3000 },
    );

    const confirmButtons = screen.getAllByRole('button', { name: /revoke/i });
    // The last "Revoke" button should be the confirmation button
    const confirmButton = confirmButtons[confirmButtons.length - 1];

    await act(async () => {
      fireEvent.click(confirmButton);
    });

    await waitFor(() => {
      expect(apiClient.delete).toHaveBeenCalledWith('/auth/sessions/2');
      expect(apiClient.get).toHaveBeenCalledTimes(2); // Initial + after revoke
    });
  });

  // Test 5: Révoque toutes les autres sessions
  it('should revoke all other sessions', async () => {
    vi.mocked(apiClient.get).mockResolvedValue({
      data: {
        sessions: mockSessions,
        count: 3,
      },
    });
    vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });

    render(<SessionsPage />);

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

    const revokeAllButton = screen.getByText('Revoke All Other Sessions');

    await act(async () => {
      fireEvent.click(revokeAllButton);
    });

    // Wait for confirmation dialog to appear and confirm
    await waitFor(
      () => {
        expect(
          screen.getByText(
            /are you sure you want to revoke all other sessions/i,
          ),
        ).toBeInTheDocument();
      },
      { timeout: 3000 },
    );

    const confirmButton = screen.getByText('Revoke All');

    await act(async () => {
      fireEvent.click(confirmButton);
    });

    await waitFor(() => {
      expect(apiClient.delete).toHaveBeenCalledWith('/auth/sessions');
      expect(apiClient.get).toHaveBeenCalledTimes(2); // Initial + after revoke all
    });
  });

  // Test 6: Ne révoque pas toutes les sessions si l'utilisateur annule
  it('should not revoke all sessions if user cancels', async () => {
    vi.mocked(apiClient.get).mockResolvedValue({
      data: {
        sessions: mockSessions,
        count: 3,
      },
    });

    render(<SessionsPage />);

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

    const revokeAllButton = screen.getByText('Revoke All Other Sessions');

    await act(async () => {
      fireEvent.click(revokeAllButton);
    });

    // Wait for confirmation dialog to appear and cancel
    await waitFor(
      () => {
        expect(
          screen.getByText(
            /are you sure you want to revoke all other sessions/i,
          ),
        ).toBeInTheDocument();
      },
      { timeout: 3000 },
    );

    const cancelButton = screen.getByText('Cancel');

    await act(async () => {
      fireEvent.click(cancelButton);
    });

    await waitFor(() => {
      expect(apiClient.delete).not.toHaveBeenCalled();
    });
  });

  // Test 7: Affiche une erreur en cas d'échec de récupération
  it('should display error when fetch fails', async () => {
    vi.mocked(apiClient.get).mockRejectedValue({
      response: {
        data: {
          error: 'Failed to load sessions',
        },
      },
    });

    render(<SessionsPage />);

    await waitFor(() => {
      expect(screen.getByText('Failed to load sessions')).toBeInTheDocument();
    });
  });

  // Test 8: Affiche une erreur en cas d'échec de révocation
  it('should display error when revoke fails', async () => {
    vi.mocked(apiClient.get).mockResolvedValue({
      data: {
        sessions: mockSessions,
        count: 3,
      },
    });
    vi.mocked(apiClient.delete).mockRejectedValue({
      response: {
        data: {
          error: 'Failed to revoke session',
        },
      },
    });

    render(<SessionsPage />);

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

    const revokeButtons = screen.getAllByRole('button', { name: /revoke/i });
    // Filter out the "Revoke All Other Sessions" button
    const sessionRevokeButtons = revokeButtons.filter(
      (btn) => !btn.textContent?.includes('All Other'),
    );

    await act(async () => {
      fireEvent.click(sessionRevokeButtons[0]);
    });

    // Wait for confirmation dialog to appear and confirm
    await waitFor(
      () => {
        expect(
          screen.getByText(/are you sure you want to revoke this session/i),
        ).toBeInTheDocument();
      },
      { timeout: 3000 },
    );

    const confirmButtons = screen.getAllByRole('button', { name: /revoke/i });
    // The last "Revoke" button should be the confirmation button
    const confirmButton = confirmButtons[confirmButtons.length - 1];

    await act(async () => {
      fireEvent.click(confirmButton);
    });

    await waitFor(() => {
      expect(screen.getByText('Failed to revoke session')).toBeInTheDocument();
    });
  });

  // Test bonus: Ne montre pas le bouton "Revoke" pour la session actuelle
  it('should not show revoke button for current session', async () => {
    vi.mocked(apiClient.get).mockResolvedValue({
      data: {
        sessions: mockSessions,
        count: 3,
      },
    });

    render(<SessionsPage />);

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

    // Il devrait y avoir 2 boutons "Revoke" (pas 3, car la session actuelle n'en a pas)
    const revokeButtons = screen.getAllByRole('button', { name: /revoke/i });
    // Filter out the "Revoke All Other Sessions" button
    const sessionRevokeButtons = revokeButtons.filter(
      (btn) => !btn.textContent?.includes('All Other'),
    );
    expect(sessionRevokeButtons).toHaveLength(2);
  });
});
