import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';
import NotificationDropdown from '../NotificationDropdown';
import { Notification } from '../../api/notifications';

// Mock react-i18next
vi.mock('react-i18next', () => ({
  useTranslation: () => ({
    t: (key: string, defaultValue?: string) => defaultValue || key,
  }),
}));

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

// Mock hooks
const mockNotifications: Notification[] = [
  {
    id: 1,
    verb: 'created',
    read: false,
    timestamp: new Date().toISOString(),
    data: {},
    actor_type: 'user',
    actor_display: 'John Doe',
    target_type: 'appointment',
    target_display: 'Appointment with Jane',
    target_url: '/appointments/1',
  },
  {
    id: 2,
    verb: 'updated',
    read: true,
    timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), // 2 hours ago
    data: {},
    actor_type: 'user',
    actor_display: 'Jane Smith',
    target_type: 'event',
    target_display: 'Meeting scheduled',
    target_url: '/events/2',
  },
  {
    id: 3,
    verb: 'created a ticket',
    read: false,
    timestamp: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), // 1 day ago
    data: { ticket_id: '123' },
    actor_type: 'user',
    actor_display: 'Support Team',
    target_type: 'ticket',
    target_display: 'Ticket #123',
    target_url: null,
  },
  {
    id: 4,
    verb: 'requested time off',
    read: false,
    timestamp: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), // 10 days ago
    data: { type: 'time_off_request' },
    actor_type: 'user',
    actor_display: 'Bob Johnson',
    target_type: null,
    target_display: 'Time off request',
    target_url: null,
  },
];

vi.mock('../../hooks/useNotifications', () => ({
  useNotifications: vi.fn(),
  useUnreadNotificationCount: vi.fn(),
  useMarkNotificationRead: vi.fn(),
  useMarkAllNotificationsRead: vi.fn(),
  useClearAllNotifications: vi.fn(),
}));

import {
  useNotifications,
  useUnreadNotificationCount,
  useMarkNotificationRead,
  useMarkAllNotificationsRead,
  useClearAllNotifications,
} from '../../hooks/useNotifications';

const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
      },
    },
  });
  return ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>{children}</BrowserRouter>
    </QueryClientProvider>
  );
};

describe('NotificationDropdown', () => {
  const mockMarkRead = vi.fn();
  const mockMarkAllRead = vi.fn();
  const mockClearAll = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();

    // Default mock implementations
    vi.mocked(useNotifications).mockReturnValue({
      data: mockNotifications,
      isLoading: false,
    } as any);

    vi.mocked(useUnreadNotificationCount).mockReturnValue({
      data: 2,
    } as any);

    vi.mocked(useMarkNotificationRead).mockReturnValue({
      mutate: mockMarkRead,
      isPending: false,
    } as any);

    vi.mocked(useMarkAllNotificationsRead).mockReturnValue({
      mutate: mockMarkAllRead,
      isPending: false,
    } as any);

    vi.mocked(useClearAllNotifications).mockReturnValue({
      mutate: mockClearAll,
      isPending: false,
    } as any);
  });

  describe('Rendering', () => {
    it('renders bell icon button', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      expect(screen.getByRole('button', { name: /open notifications/i })).toBeInTheDocument();
    });

    it('displays unread count badge when there are unread notifications', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      expect(screen.getByText('2')).toBeInTheDocument();
    });

    it('does not display badge when unread count is 0', () => {
      vi.mocked(useUnreadNotificationCount).mockReturnValue({
        data: 0,
      } as any);

      render(<NotificationDropdown />, { wrapper: createWrapper() });
      expect(screen.queryByText('2')).not.toBeInTheDocument();
    });

    it('displays "99+" when unread count exceeds 99', () => {
      vi.mocked(useUnreadNotificationCount).mockReturnValue({
        data: 150,
      } as any);

      render(<NotificationDropdown />, { wrapper: createWrapper() });
      expect(screen.getByText('99+')).toBeInTheDocument();
    });

    it('does not render dropdown when closed', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      expect(screen.queryByText('Notifications')).not.toBeInTheDocument();
    });
  });

  describe('Dropdown interactions', () => {
    it('opens dropdown when bell icon is clicked', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));
      expect(screen.getByText('Notifications')).toBeInTheDocument();
    });

    it('closes dropdown when close button is clicked', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const closeButtons = screen.getAllByRole('button');
      const closeButton = closeButtons.find(btn => btn.querySelector('svg'));
      fireEvent.click(closeButton!);

      expect(screen.queryByText('Notifications')).not.toBeInTheDocument();
    });

    it('closes dropdown when clicking outside', async () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));
      expect(screen.getByText('Notifications')).toBeInTheDocument();

      // Simulate clicking outside
      fireEvent.mouseDown(document.body);

      await waitFor(() => {
        expect(screen.queryByText('Notifications')).not.toBeInTheDocument();
      });
    });
  });

  describe('Notification list', () => {
    it('displays all notifications when dropdown is open', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      expect(screen.getByText('John Doe')).toBeInTheDocument();
      expect(screen.getByText('Jane Smith')).toBeInTheDocument();
      expect(screen.getByText('Support Team')).toBeInTheDocument();
      expect(screen.getByText('Bob Johnson')).toBeInTheDocument();
    });

    it('displays loading state', () => {
      vi.mocked(useNotifications).mockReturnValue({
        data: [],
        isLoading: true,
      } as any);

      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      expect(screen.getByText('common.loading')).toBeInTheDocument();
    });

    it('displays empty state when no notifications', () => {
      vi.mocked(useNotifications).mockReturnValue({
        data: [],
        isLoading: false,
      } as any);

      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      expect(screen.getByText('No notifications yet')).toBeInTheDocument();
    });

    it('highlights unread notifications', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const notificationButtons = screen.getAllByRole('button');
      const unreadNotification = notificationButtons.find(btn =>
        btn.textContent?.includes('John Doe')
      );

      expect(unreadNotification).toHaveClass('bg-blue-50/50');
    });
  });

  describe('Notification actions', () => {
    it('marks notification as read when clicked', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const notification = screen.getByText('John Doe').closest('button');
      fireEvent.click(notification!);

      expect(mockMarkRead).toHaveBeenCalledWith(1);
    });

    it('navigates to target URL when notification is clicked', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const notification = screen.getByText('John Doe').closest('button');
      fireEvent.click(notification!);

      expect(mockNavigate).toHaveBeenCalledWith('/appointments/1');
    });

    it('calls onTicketClick for ticket notifications', () => {
      const mockOnTicketClick = vi.fn();
      render(<NotificationDropdown onTicketClick={mockOnTicketClick} />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const ticketNotification = screen.getByText('Support Team').closest('button');
      fireEvent.click(ticketNotification!);

      expect(mockOnTicketClick).toHaveBeenCalledWith('123');
    });

    it('navigates to time-blocks for time off requests', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const timeOffNotification = screen.getByText('Bob Johnson').closest('button');
      fireEvent.click(timeOffNotification!);

      expect(mockNavigate).toHaveBeenCalledWith('/time-blocks');
    });

    it('marks all notifications as read', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      // Find the mark all read button (CheckCheck icon)
      const buttons = screen.getAllByRole('button');
      const markAllReadButton = buttons.find(btn =>
        btn.getAttribute('title')?.includes('Mark all as read')
      );

      fireEvent.click(markAllReadButton!);
      expect(mockMarkAllRead).toHaveBeenCalled();
    });

    it('clears all read notifications', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const clearButton = screen.getByText('Clear read');
      fireEvent.click(clearButton);

      expect(mockClearAll).toHaveBeenCalled();
    });

    it('navigates to notifications page when "View all" is clicked', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const viewAllButton = screen.getByText('View all');
      fireEvent.click(viewAllButton);

      expect(mockNavigate).toHaveBeenCalledWith('/notifications');
    });
  });

  describe('Notification icons', () => {
    it('displays Clock icon for time off requests', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const timeOffNotification = screen.getByText('Bob Johnson').closest('button');
      const icon = timeOffNotification?.querySelector('svg');

      expect(icon).toBeInTheDocument();
    });

    it('displays Ticket icon for ticket notifications', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const ticketNotification = screen.getByText('Support Team').closest('button');
      const icon = ticketNotification?.querySelector('svg');

      expect(icon).toBeInTheDocument();
    });

    it('displays Calendar icon for event notifications', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const eventNotification = screen.getByText('Jane Smith').closest('button');
      const icon = eventNotification?.querySelector('svg');

      expect(icon).toBeInTheDocument();
    });
  });

  describe('Timestamp formatting', () => {
    it('displays "Just now" for recent notifications', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      // The first notification is just now
      expect(screen.getByText('Just now')).toBeInTheDocument();
    });

    it('displays relative time for older notifications', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      // Check if notification timestamps are rendered
      // We have 4 notifications in our mock data, each should have a timestamp
      const notificationButtons = screen.getAllByRole('button').filter(btn =>
        btn.textContent?.includes('John Doe') ||
        btn.textContent?.includes('Jane Smith') ||
        btn.textContent?.includes('Support Team') ||
        btn.textContent?.includes('Bob Johnson')
      );

      expect(notificationButtons.length).toBeGreaterThan(0);
      // At least one notification should have a timestamp
      const hasTimestamp = notificationButtons.some(btn => btn.textContent?.match(/Just now|\d+[hmd] ago|\d{1,2}\/\d{1,2}\/\d{4}/));
      expect(hasTimestamp).toBe(true);
    });
  });

  describe('Variants', () => {
    it('renders with light variant', () => {
      render(<NotificationDropdown variant="light" />, { wrapper: createWrapper() });
      const button = screen.getByRole('button', { name: /open notifications/i });
      expect(button).toHaveClass('text-white/80');
    });

    it('renders with dark variant (default)', () => {
      render(<NotificationDropdown variant="dark" />, { wrapper: createWrapper() });
      const button = screen.getByRole('button', { name: /open notifications/i });
      expect(button).toHaveClass('text-gray-400');
    });
  });

  describe('Loading states', () => {
    it('disables mark all read button when mutation is pending', () => {
      vi.mocked(useMarkAllNotificationsRead).mockReturnValue({
        mutate: mockMarkAllRead,
        isPending: true,
      } as any);

      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const buttons = screen.getAllByRole('button');
      const markAllReadButton = buttons.find(btn =>
        btn.getAttribute('title')?.includes('Mark all as read')
      );

      expect(markAllReadButton).toBeDisabled();
    });

    it('disables clear all button when mutation is pending', () => {
      vi.mocked(useClearAllNotifications).mockReturnValue({
        mutate: mockClearAll,
        isPending: true,
      } as any);

      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      const clearButton = screen.getByText('Clear read');
      expect(clearButton).toBeDisabled();
    });
  });

  describe('Footer visibility', () => {
    it('shows footer when there are notifications', () => {
      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      expect(screen.getByText('Clear read')).toBeInTheDocument();
      expect(screen.getByText('View all')).toBeInTheDocument();
    });

    it('hides footer when there are no notifications', () => {
      vi.mocked(useNotifications).mockReturnValue({
        data: [],
        isLoading: false,
      } as any);

      render(<NotificationDropdown />, { wrapper: createWrapper() });
      fireEvent.click(screen.getByRole('button', { name: /open notifications/i }));

      expect(screen.queryByText('Clear read')).not.toBeInTheDocument();
      expect(screen.queryByText('View all')).not.toBeInTheDocument();
    });
  });
});
