// src/features/flyer/FlyerList.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach, type Mocked } from 'vitest';
import { FlyerList } from './FlyerList';
import { formatShortDate } from './dateUtils';
import type { Flyer, UserProfile } from '../../types';
import { createMockUserProfile } from '../../tests/utils/mockFactories';
import { createMockFlyer } from '../../tests/utils/mockFactories';
import * as apiClient from '../../services/apiClient';
import toast from 'react-hot-toast';

// Mock the apiClient and toast notifications
vi.mock('../../services/apiClient');
vi.mock('react-hot-toast', () => ({ default: { success: vi.fn(), error: vi.fn() } }));

const mockFlyers: Flyer[] = [
  createMockFlyer({
    flyer_id: 1,
    file_name: 'metro_flyer_oct_1.pdf',
    item_count: 50,
    image_url: 'http://example.com/flyer1.jpg',
    store: { store_id: 101, name: 'Metro' },
    valid_from: '2023-10-05',
    valid_to: '2023-10-11',
    created_at: '2023-10-01T10:00:00Z',
  }),
  createMockFlyer({
    flyer_id: 2,
    file_name: 'walmart_flyer.pdf',
    item_count: 75,
    image_url: 'http://example.com/flyer2.jpg',
    store: { store_id: 102, name: 'Walmart' },
    valid_from: '2023-10-06',
    valid_to: '2023-10-06', // Same day
    icon_url: '', // Force empty to test default icon rendering
  }),
  {
    ...createMockFlyer({
      flyer_id: 3,
      file_name: 'no-store-flyer.pdf',
      item_count: 10,
      image_url: 'http://example.com/flyer3.jpg',
      icon_url: 'http://example.com/icon3.png',
      valid_from: '2023-10-07',
      valid_to: '2023-10-08',
      store_address: '456 Side St, Ottawa',
      created_at: '2023-10-03T12:00:00Z',
    }),
    store: null as any, // Force null to ensure "Unknown Store" fallback triggers (overriding factory defaults)
  },
  createMockFlyer({
    flyer_id: 4,
    file_name: 'bad-date-flyer.pdf',
    item_count: 5,
    image_url: 'http://example.com/flyer4.jpg',
    store: { store_id: 103, name: 'Date Store' },
    created_at: 'invalid-date',
    valid_from: 'invalid-from',
    valid_to: null,
  }),
];

const mockedApiClient = apiClient as Mocked<typeof apiClient>;
const mockedToast = toast as Mocked<typeof toast>;

describe('FlyerList', () => {
  const mockOnFlyerSelect = vi.fn();
  const mockProfile: UserProfile = createMockUserProfile({
    user: { user_id: '1', email: 'test@example.com' },
    role: 'user',
  });

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

  it('should render the heading', () => {
    render(
      <FlyerList
        flyers={[]}
        onFlyerSelect={mockOnFlyerSelect}
        selectedFlyerId={null}
        profile={null}
      />,
    );
    expect(screen.getByRole('heading', { name: /processed flyers/i })).toBeInTheDocument();
  });

  it('should display a message when there are no flyers', () => {
    render(
      <FlyerList
        flyers={[]}
        onFlyerSelect={mockOnFlyerSelect}
        selectedFlyerId={null}
        profile={null}
      />,
    );
    expect(screen.getByText(/no flyers have been processed yet/i)).toBeInTheDocument();
  });

  it('should render a list of flyers with correct details', () => {
    render(
      <FlyerList
        flyers={mockFlyers}
        onFlyerSelect={mockOnFlyerSelect}
        selectedFlyerId={null}
        profile={mockProfile}
      />,
    );

    // Check first flyer
    expect(screen.getByText('Metro')).toBeInTheDocument();
    expect(screen.getByText(/50 items/i)).toBeInTheDocument();
    expect(screen.getByText(/Valid: Oct 5 - Oct 11/i)).toBeInTheDocument();

    // Check second flyer
    expect(screen.getByText('Walmart')).toBeInTheDocument();
    expect(screen.getByText(/Valid: Oct 6/i)).toBeInTheDocument(); // Single day validity
  });

  it('should call onFlyerSelect with the correct flyer when an item is clicked', () => {
    render(
      <FlyerList
        flyers={mockFlyers}
        onFlyerSelect={mockOnFlyerSelect}
        selectedFlyerId={null}
        profile={mockProfile}
      />,
    );

    const firstFlyerItem = screen.getByText('Metro').closest('li');
    fireEvent.click(firstFlyerItem!);

    expect(mockOnFlyerSelect).toHaveBeenCalledTimes(1);
    expect(mockOnFlyerSelect).toHaveBeenCalledWith(mockFlyers[0]);
  });

  it('should apply a selected style to the currently selected flyer', () => {
    render(
      <FlyerList
        flyers={mockFlyers}
        onFlyerSelect={mockOnFlyerSelect}
        selectedFlyerId={1}
        profile={mockProfile}
      />,
    );

    const selectedItem = screen.getByText('Metro').closest('li');
    expect(selectedItem).toHaveClass('bg-brand-light', 'dark:bg-brand-dark/30');
  });

  describe('UI Details and Edge Cases', () => {
    it('should render an image icon when icon_url is present', () => {
      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      const flyerWithIcon = screen.getByText('Unknown Store').closest('li'); // Flyer ID 3
      const iconImage = flyerWithIcon?.querySelector('img');
      expect(iconImage).toBeInTheDocument();
      expect(iconImage).toHaveAttribute('src', 'http://example.com/icon3.png');
    });

    it('should render a document icon when icon_url is not present', () => {
      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      const flyerWithoutIcon = screen.getByText('Walmart').closest('li'); // Flyer ID 2
      const iconImage = flyerWithoutIcon?.querySelector('img');
      const documentIcon = flyerWithoutIcon?.querySelector('svg');
      expect(iconImage).not.toBeInTheDocument();
      expect(documentIcon).toBeInTheDocument();
    });

    it('should render "Unknown Store" if store data is missing', () => {
      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      expect(screen.getByText('Unknown Store')).toBeInTheDocument();
    });

    it('should render a map link if store_address is present and stop propagation on click', () => {
      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      const flyerWithAddress = screen.getByText('Unknown Store').closest('li');
      const mapLink = flyerWithAddress?.querySelector('a');
      expect(mapLink).toBeInTheDocument();
      expect(mapLink).toHaveAttribute(
        'href',
        'https://www.google.com/maps/search/?api=1&query=456%20Side%20St%2C%20Ottawa',
      );

      // Test that clicking the map link does not select the flyer
      fireEvent.click(mapLink!);
      expect(mockOnFlyerSelect).not.toHaveBeenCalled();
    });

    it('should render a detailed tooltip', () => {
      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      const firstFlyerItem = screen.getByText('Metro').closest('li');
      const tooltipText = firstFlyerItem?.getAttribute('title');
      expect(tooltipText).toContain('File: metro_flyer_oct_1.pdf');
      expect(tooltipText).toContain('Store: Metro');
      expect(tooltipText).toContain('Items: 50');
      expect(tooltipText).toContain('Deals valid from October 5, 2023 to October 11, 2023');
      // Use a regex for the processed time to avoid timezone-related flakiness in tests.
      expect(tooltipText).toMatch(/Processed: October 1, 2023 at \d{1,2}:\d{2}:\d{2} (AM|PM)/);
    });

    it('should handle invalid dates gracefully in display and tooltip', () => {
      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      const badDateItem = screen.getByText('Date Store').closest('li');

      // Display should not show "Valid:" text if dates are invalid
      expect(badDateItem).toHaveTextContent('5 items');
      expect(badDateItem).not.toHaveTextContent(/Valid:/);

      // Tooltip should show N/A for invalid dates
      const tooltipText = badDateItem?.getAttribute('title');
      expect(tooltipText).toContain('Validity: N/A');
      expect(tooltipText).toContain('Processed: N/A');
    });
  });

  describe('Expiration Status Logic', () => {
    beforeEach(() => {
      vi.useFakeTimers();
    });

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

    it('should show "Expired" for past dates', () => {
      // Flyer 1 valid_to is 2023-10-11
      vi.setSystemTime(new Date('2023-10-12T12:00:00Z'));
      render(
        <FlyerList
          flyers={[mockFlyers[0]]}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      expect(screen.getByText('• Expired')).toBeInTheDocument();
      expect(screen.getByText('• Expired')).toHaveClass('text-red-500');
    });

    it('should show "Expires today" when valid_to is today', () => {
      vi.setSystemTime(new Date('2023-10-11T12:00:00Z'));
      render(
        <FlyerList
          flyers={[mockFlyers[0]]}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      expect(screen.getByText('• Expires today')).toBeInTheDocument();
      expect(screen.getByText('• Expires today')).toHaveClass('text-orange-500');
    });

    it('should show "Expires in X days" (orange) for <= 3 days', () => {
      vi.setSystemTime(new Date('2023-10-09T12:00:00Z')); // 2 days left
      render(
        <FlyerList
          flyers={[mockFlyers[0]]}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      expect(screen.getByText('• Expires in 2 days')).toBeInTheDocument();
      expect(screen.getByText('• Expires in 2 days')).toHaveClass('text-orange-500');
    });

    it('should show "Expires in X days" (green) for > 3 days', () => {
      vi.setSystemTime(new Date('2023-10-05T12:00:00Z')); // 6 days left
      render(
        <FlyerList
          flyers={[mockFlyers[0]]}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      expect(screen.getByText('• Expires in 6 days')).toBeInTheDocument();
      expect(screen.getByText('• Expires in 6 days')).toHaveClass('text-green-600');
    });
  });

  describe('Admin Functionality', () => {
    const adminProfile: UserProfile = createMockUserProfile({
      user: { user_id: 'admin-1', email: 'admin@example.com' },
      role: 'admin',
    });

    it('should not show the cleanup button for non-admin users', () => {
      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={mockProfile}
        />,
      );
      expect(screen.queryByTitle(/clean up files/i)).not.toBeInTheDocument();
    });

    it('should show the cleanup button for admin users', () => {
      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={adminProfile}
        />,
      );
      expect(screen.getByTitle('Clean up files for flyer ID 1')).toBeInTheDocument();
      expect(screen.getByTitle('Clean up files for flyer ID 2')).toBeInTheDocument();
    });

    it('should call cleanupFlyerFiles when admin clicks and confirms', async () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
      mockedApiClient.cleanupFlyerFiles.mockResolvedValue(new Response(null, { status: 200 }));

      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={adminProfile}
        />,
      );

      const cleanupButton = screen.getByTitle('Clean up files for flyer ID 1');
      fireEvent.click(cleanupButton);

      expect(confirmSpy).toHaveBeenCalledWith(
        'Are you sure you want to clean up the files for flyer ID 1? This action cannot be undone.',
      );
      await waitFor(() => {
        expect(mockedApiClient.cleanupFlyerFiles).toHaveBeenCalledWith(1);
      });
    });

    it('should not call cleanupFlyerFiles when admin clicks and cancels', () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false);

      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={adminProfile}
        />,
      );

      const cleanupButton = screen.getByTitle('Clean up files for flyer ID 1');
      fireEvent.click(cleanupButton);

      expect(confirmSpy).toHaveBeenCalled();
      expect(mockedApiClient.cleanupFlyerFiles).not.toHaveBeenCalled();
    });

    it('should show an error toast if cleanup API call fails', async () => {
      vi.spyOn(window, 'confirm').mockReturnValue(true);
      const apiError = new Error('Cleanup failed');
      mockedApiClient.cleanupFlyerFiles.mockRejectedValue(apiError);

      render(
        <FlyerList
          flyers={mockFlyers}
          onFlyerSelect={mockOnFlyerSelect}
          selectedFlyerId={null}
          profile={adminProfile}
        />,
      );

      const cleanupButton = screen.getByTitle('Clean up files for flyer ID 1');
      fireEvent.click(cleanupButton);

      await waitFor(() => {
        expect(mockedApiClient.cleanupFlyerFiles).toHaveBeenCalledWith(1);
        expect(mockedToast.error).toHaveBeenCalledWith('Cleanup failed');
      });
    });
  });
});

describe('formatShortDate', () => {
  it('should return null for null, undefined, or empty string input', () => {
    expect(formatShortDate(null)).toBeNull();
    expect(formatShortDate(undefined)).toBeNull();
    expect(formatShortDate('')).toBeNull();
  });

  it('should correctly format a valid ISO date string (YYYY-MM-DD)', () => {
    expect(formatShortDate('2023-10-05')).toBe('Oct 5');
    expect(formatShortDate('2024-01-20')).toBe('Jan 20');
    expect(formatShortDate('2023-12-31')).toBe('Dec 31');
  });

  it('should return null for various invalid date string formats', () => {
    expect(formatShortDate('invalid-date-string')).toBeNull();
    expect(formatShortDate('2023-20-20')).toBeNull(); // Invalid month
    expect(formatShortDate('2023-02-30')).toBeNull(); // Invalid day
    expect(formatShortDate('not a date')).toBeNull();
  });
});
