import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';
import Locations from '../Locations';
import { Location } from '../../types';

// Mock the hooks
vi.mock('../../hooks/useLocations', () => ({
  useLocations: vi.fn(),
  useCreateLocation: vi.fn(),
  useUpdateLocation: vi.fn(),
  useDeleteLocation: vi.fn(),
  useSetPrimaryLocation: vi.fn(),
  useSetLocationActive: vi.fn(),
}));

vi.mock('react-i18next', () => ({
  useTranslation: () => ({
    t: (key: string) => key,
    i18n: { language: 'en' },
  }),
}));

import {
  useLocations,
  useCreateLocation,
  useUpdateLocation,
  useDeleteLocation,
  useSetPrimaryLocation,
  useSetLocationActive,
} from '../../hooks/useLocations';

// Helper to create mock locations
const createMockLocation = (overrides?: Partial<Location>): Location => ({
  id: 1,
  name: 'Main Office',
  address_line1: '123 Main St',
  address_line2: '',
  city: 'Denver',
  state: 'CO',
  postal_code: '80202',
  country: 'US',
  phone: '555-1234',
  email: 'office@example.com',
  timezone: 'America/Denver',
  is_active: true,
  is_primary: true,
  display_order: 0,
  resource_count: 5,
  service_count: 3,
  ...overrides,
});

// Helper to create mock mutation
const createMockMutation = (overrides: Partial<ReturnType<typeof useCreateLocation>> = {}) => ({
  mutateAsync: vi.fn(),
  isPending: false,
  error: null,
  ...overrides,
});

// Create wrapper with QueryClient
const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  return function Wrapper({ children }: { children: React.ReactNode }) {
    return React.createElement(QueryClientProvider, { client: queryClient }, children);
  };
};

describe('Locations Page', () => {
  beforeEach(() => {
    vi.clearAllMocks();

    // Default mock implementations
    vi.mocked(useCreateLocation).mockReturnValue(createMockMutation() as any);
    vi.mocked(useUpdateLocation).mockReturnValue(createMockMutation() as any);
    vi.mocked(useDeleteLocation).mockReturnValue(createMockMutation() as any);
    vi.mocked(useSetPrimaryLocation).mockReturnValue(createMockMutation() as any);
    vi.mocked(useSetLocationActive).mockReturnValue(createMockMutation() as any);
  });

  describe('Loading State', () => {
    it('shows loading spinner while fetching locations', () => {
      vi.mocked(useLocations).mockReturnValue({
        data: undefined,
        isLoading: true,
        error: null,
      } as any);

      const { container } = render(<Locations />, { wrapper: createWrapper() });

      // Check for spinner by class name
      expect(container.querySelector('.animate-spin')).toBeTruthy();
    });
  });

  describe('Error State', () => {
    it('shows error alert when locations fail to load', () => {
      vi.mocked(useLocations).mockReturnValue({
        data: undefined,
        isLoading: false,
        error: new Error('Network error'),
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      // The component shows an Alert with role="alert"
      expect(screen.getByRole('alert')).toBeInTheDocument();
    });
  });

  describe('Empty State', () => {
    it('shows empty state when no locations exist', () => {
      vi.mocked(useLocations).mockReturnValue({
        data: [],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      expect(screen.getByText(/no locations yet/i)).toBeInTheDocument();
      expect(screen.getByText(/get started by creating your first location/i)).toBeInTheDocument();
    });

    it('shows add location button in empty state', () => {
      vi.mocked(useLocations).mockReturnValue({
        data: [],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      const addButtons = screen.getAllByRole('button', { name: /add location/i });
      expect(addButtons.length).toBeGreaterThanOrEqual(1);
    });
  });

  describe('Locations List', () => {
    it('renders locations with their names', () => {
      const locations = [
        createMockLocation({ id: 1, name: 'Main Office', is_primary: true }),
        createMockLocation({ id: 2, name: 'Downtown Branch', is_primary: false }),
      ];

      vi.mocked(useLocations).mockReturnValue({
        data: locations,
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      expect(screen.getByText('Main Office')).toBeInTheDocument();
      expect(screen.getByText('Downtown Branch')).toBeInTheDocument();
    });

    it('shows primary badge on primary location', () => {
      const locations = [
        createMockLocation({ id: 1, name: 'Main Office', is_primary: true }),
      ];

      vi.mocked(useLocations).mockReturnValue({
        data: locations,
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      expect(screen.getByText('Primary')).toBeInTheDocument();
    });

    it('shows inactive badge on inactive locations', () => {
      const locations = [
        createMockLocation({ id: 1, name: 'Closed Branch', is_active: false, is_primary: false }),
      ];

      vi.mocked(useLocations).mockReturnValue({
        data: locations,
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      expect(screen.getByText('Inactive')).toBeInTheDocument();
    });

    it('shows resource and service counts', () => {
      const locations = [
        createMockLocation({ id: 1, resource_count: 5, service_count: 3 }),
      ];

      vi.mocked(useLocations).mockReturnValue({
        data: locations,
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      expect(screen.getByText('5 resources')).toBeInTheDocument();
      expect(screen.getByText('3 services')).toBeInTheDocument();
    });

    it('shows address information', () => {
      const locations = [
        createMockLocation({
          id: 1,
          address_line1: '123 Main St',
          city: 'Denver',
          state: 'CO',
          postal_code: '80202',
        }),
      ];

      vi.mocked(useLocations).mockReturnValue({
        data: locations,
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      expect(screen.getByText(/123 Main St.*Denver.*CO.*80202/)).toBeInTheDocument();
    });
  });

  describe('Create Location', () => {
    it('opens modal when clicking Add Location button', async () => {
      vi.mocked(useLocations).mockReturnValue({
        data: [createMockLocation()],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      const addButton = screen.getByRole('button', { name: /add location/i });
      await userEvent.click(addButton);

      // Check modal title - there will be two "Add Location" texts: button and modal title
      const addLocationElements = screen.getAllByText(/add location/i);
      expect(addLocationElements.length).toBeGreaterThanOrEqual(2);
      expect(screen.getByLabelText(/location name/i)).toBeInTheDocument();
    });

    it('submits form data when creating location', async () => {
      const mockCreateMutation = vi.fn().mockResolvedValue({});
      vi.mocked(useCreateLocation).mockReturnValue({
        mutateAsync: mockCreateMutation,
        isPending: false,
        error: null,
      } as any);

      vi.mocked(useLocations).mockReturnValue({
        data: [],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      const addButton = screen.getAllByRole('button', { name: /add location/i })[0];
      await userEvent.click(addButton);

      // Fill in form
      await userEvent.type(screen.getByLabelText(/location name/i), 'New Branch');
      await userEvent.type(screen.getByLabelText(/address line 1/i), '456 New St');

      // Submit
      const createButton = screen.getByRole('button', { name: /create location/i });
      await userEvent.click(createButton);

      expect(mockCreateMutation).toHaveBeenCalledWith(
        expect.objectContaining({
          name: 'New Branch',
          address_line1: '456 New St',
        })
      );
    });
  });

  describe('Edit Location', () => {
    it('opens edit modal with location data', async () => {
      const location = createMockLocation({ name: 'Main Office' });

      vi.mocked(useLocations).mockReturnValue({
        data: [location],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      // Open menu
      const menuButton = screen.getByRole('button', { name: '' }); // More options button
      await userEvent.click(menuButton);

      // Click edit
      const editButton = screen.getByRole('button', { name: /edit/i });
      await userEvent.click(editButton);

      // Modal should show with location name
      expect(screen.getByText('Edit Location')).toBeInTheDocument();
      expect(screen.getByDisplayValue('Main Office')).toBeInTheDocument();
    });

    it('submits updated data when editing', async () => {
      const mockUpdateMutation = vi.fn().mockResolvedValue({});
      vi.mocked(useUpdateLocation).mockReturnValue({
        mutateAsync: mockUpdateMutation,
        isPending: false,
        error: null,
      } as any);

      const location = createMockLocation({ id: 1, name: 'Main Office' });

      vi.mocked(useLocations).mockReturnValue({
        data: [location],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      // Open menu and click edit
      const menuButton = screen.getByRole('button', { name: '' });
      await userEvent.click(menuButton);
      await userEvent.click(screen.getByRole('button', { name: /edit/i }));

      // Update the name
      const nameInput = screen.getByDisplayValue('Main Office');
      await userEvent.clear(nameInput);
      await userEvent.type(nameInput, 'Updated Office');

      // Submit
      await userEvent.click(screen.getByRole('button', { name: /save changes/i }));

      expect(mockUpdateMutation).toHaveBeenCalledWith({
        id: 1,
        updates: expect.objectContaining({ name: 'Updated Office' }),
      });
    });
  });

  describe('Delete Location', () => {
    it('renders delete button in dropdown menu', async () => {
      const location = createMockLocation({ name: 'Branch to Delete', is_primary: false });

      vi.mocked(useLocations).mockReturnValue({
        data: [location],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      // Find buttons - the component should have menu buttons
      const allButtons = screen.getAllByRole('button');
      // Should have Add Location button and at least one card menu button
      expect(allButtons.length).toBeGreaterThan(1);
    });

    it('calls delete mutation when confirming', async () => {
      const mockDeleteMutation = vi.fn().mockResolvedValue({});
      vi.mocked(useDeleteLocation).mockReturnValue({
        mutateAsync: mockDeleteMutation,
        isPending: false,
        error: null,
      } as any);

      const location = createMockLocation({ id: 5, name: 'To Delete' });

      vi.mocked(useLocations).mockReturnValue({
        data: [location],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      // Open menu and click delete
      await userEvent.click(screen.getByRole('button', { name: '' }));
      await userEvent.click(screen.getByRole('button', { name: /delete/i }));

      // Confirm deletion
      const confirmDeleteButton = screen.getAllByRole('button', { name: /delete/i })[1];
      await userEvent.click(confirmDeleteButton);

      expect(mockDeleteMutation).toHaveBeenCalledWith(5);
    });
  });

  describe('Set Primary Location', () => {
    it('shows set as primary option for non-primary locations', async () => {
      const locations = [
        createMockLocation({ id: 1, name: 'Primary', is_primary: true }),
        createMockLocation({ id: 2, name: 'Secondary', is_primary: false }),
      ];

      vi.mocked(useLocations).mockReturnValue({
        data: locations,
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      // Open menu for secondary location
      const menuButtons = screen.getAllByRole('button', { name: '' });
      await userEvent.click(menuButtons[1]);

      expect(screen.getByRole('button', { name: /set as primary/i })).toBeInTheDocument();
    });

    it('hides set as primary for already primary location', async () => {
      const location = createMockLocation({ is_primary: true });

      vi.mocked(useLocations).mockReturnValue({
        data: [location],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      // Open menu
      await userEvent.click(screen.getByRole('button', { name: '' }));

      expect(screen.queryByRole('button', { name: /set as primary/i })).not.toBeInTheDocument();
    });

    it('calls set primary mutation', async () => {
      const mockSetPrimaryMutation = vi.fn().mockResolvedValue({});
      vi.mocked(useSetPrimaryLocation).mockReturnValue({
        mutateAsync: mockSetPrimaryMutation,
        isPending: false,
        error: null,
      } as any);

      const location = createMockLocation({ id: 2, is_primary: false });

      vi.mocked(useLocations).mockReturnValue({
        data: [location],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      // Open menu and click set primary
      await userEvent.click(screen.getByRole('button', { name: '' }));
      await userEvent.click(screen.getByRole('button', { name: /set as primary/i }));

      expect(mockSetPrimaryMutation).toHaveBeenCalledWith(2);
    });
  });

  describe('Activate/Deactivate Location', () => {
    it('shows deactivate option for active locations', async () => {
      const location = createMockLocation({ is_active: true });

      vi.mocked(useLocations).mockReturnValue({
        data: [location],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      await userEvent.click(screen.getByRole('button', { name: '' }));

      expect(screen.getByRole('button', { name: /deactivate/i })).toBeInTheDocument();
    });

    it('shows activate option for inactive locations', async () => {
      const location = createMockLocation({ is_active: false, is_primary: false });

      vi.mocked(useLocations).mockReturnValue({
        data: [location],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      await userEvent.click(screen.getByRole('button', { name: '' }));

      expect(screen.getByRole('button', { name: /activate/i })).toBeInTheDocument();
    });

    it('calls set active mutation with correct value', async () => {
      const mockSetActiveMutation = vi.fn().mockResolvedValue({});
      vi.mocked(useSetLocationActive).mockReturnValue({
        mutateAsync: mockSetActiveMutation,
        isPending: false,
        error: null,
      } as any);

      const location = createMockLocation({ id: 3, is_active: true });

      vi.mocked(useLocations).mockReturnValue({
        data: [location],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      await userEvent.click(screen.getByRole('button', { name: '' }));
      await userEvent.click(screen.getByRole('button', { name: /deactivate/i }));

      expect(mockSetActiveMutation).toHaveBeenCalledWith({
        id: 3,
        isActive: false,
      });
    });
  });

  describe('Error Display', () => {
    it('shows error alert when mutation fails', async () => {
      const deleteError = {
        response: {
          data: {
            detail: 'Cannot delete the last active location.',
          },
        },
      };

      vi.mocked(useDeleteLocation).mockReturnValue({
        mutateAsync: vi.fn(),
        isPending: false,
        error: deleteError,
      } as any);

      vi.mocked(useLocations).mockReturnValue({
        data: [createMockLocation()],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      // The component renders an Alert when any mutation has an error
      expect(screen.getByRole('alert')).toBeInTheDocument();
    });

    it('shows error alert for set active errors', async () => {
      const setActiveError = {
        response: {
          data: {
            detail: 'Cannot deactivate or delete the last active location.',
          },
        },
      };

      vi.mocked(useSetLocationActive).mockReturnValue({
        mutateAsync: vi.fn(),
        isPending: false,
        error: setActiveError,
      } as any);

      vi.mocked(useLocations).mockReturnValue({
        data: [createMockLocation()],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      expect(screen.getByRole('alert')).toBeInTheDocument();
    });

    it('shows error alert for quota errors', async () => {
      const quotaError = {
        response: {
          data: {
            detail: 'Location quota reached (3). Upgrade your plan for more locations.',
          },
        },
      };

      vi.mocked(useSetLocationActive).mockReturnValue({
        mutateAsync: vi.fn(),
        isPending: false,
        error: quotaError,
      } as any);

      vi.mocked(useLocations).mockReturnValue({
        data: [createMockLocation()],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      expect(screen.getByRole('alert')).toBeInTheDocument();
    });

    it('shows error alert when create fails', async () => {
      const quotaError = {
        response: {
          data: {
            detail: 'Location quota reached (5). Upgrade your plan for more locations.',
          },
        },
      };

      vi.mocked(useCreateLocation).mockReturnValue({
        mutateAsync: vi.fn(),
        isPending: false,
        error: quotaError,
      } as any);

      vi.mocked(useLocations).mockReturnValue({
        data: [createMockLocation()],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      expect(screen.getByRole('alert')).toBeInTheDocument();
    });
  });

  describe('Page Header', () => {
    it('renders page title and description', () => {
      vi.mocked(useLocations).mockReturnValue({
        data: [createMockLocation()],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      expect(screen.getByText('Locations')).toBeInTheDocument();
      expect(screen.getByText(/manage your business locations/i)).toBeInTheDocument();
    });

    it('renders add location button in header', () => {
      vi.mocked(useLocations).mockReturnValue({
        data: [createMockLocation()],
        isLoading: false,
        error: null,
      } as any);

      render(<Locations />, { wrapper: createWrapper() });

      const addButton = screen.getByRole('button', { name: /add location/i });
      expect(addButton).toBeInTheDocument();
    });
  });
});
