import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';

vi.mock('../../api/client', () => ({
  default: {
    get: vi.fn(),
  },
}));

import { useResourceLocation, useLiveResourceLocation } from '../useResourceLocation';
import apiClient from '../../api/client';

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('useResourceLocation', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('data transformation', () => {
    it('should transform snake_case to camelCase for basic location data', async () => {
      const mockResponse = {
        data: {
          has_location: true,
          latitude: 40.7128,
          longitude: -74.0060,
          accuracy: 10,
          heading: 180,
          speed: 5.5,
          timestamp: '2025-12-07T12:00:00Z',
          is_tracking: true,
          message: 'Location updated',
        },
      };

      vi.mocked(apiClient.get).mockResolvedValueOnce(mockResponse);

      const { result } = renderHook(() => useResourceLocation('123'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      expect(result.current.data).toEqual({
        hasLocation: true,
        latitude: 40.7128,
        longitude: -74.0060,
        accuracy: 10,
        heading: 180,
        speed: 5.5,
        timestamp: '2025-12-07T12:00:00Z',
        isTracking: true,
        activeJob: null,
        message: 'Location updated',
      });
    });

    it('should transform activeJob with status_display to statusDisplay', async () => {
      const mockResponse = {
        data: {
          has_location: true,
          latitude: 40.7128,
          longitude: -74.0060,
          is_tracking: true,
          active_job: {
            id: 456,
            title: 'Repair HVAC System',
            status: 'en_route',
            status_display: 'En Route',
          },
        },
      };

      vi.mocked(apiClient.get).mockResolvedValueOnce(mockResponse);

      const { result } = renderHook(() => useResourceLocation('123'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      expect(result.current.data?.activeJob).toEqual({
        id: 456,
        title: 'Repair HVAC System',
        status: 'en_route',
        statusDisplay: 'En Route',
      });
    });

    it('should set activeJob to null when not provided', async () => {
      const mockResponse = {
        data: {
          has_location: false,
          is_tracking: false,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValueOnce(mockResponse);

      const { result } = renderHook(() => useResourceLocation('123'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      expect(result.current.data?.activeJob).toBeNull();
    });

    it('should default isTracking to false when not provided', async () => {
      const mockResponse = {
        data: {
          has_location: true,
          latitude: 40.7128,
          longitude: -74.0060,
          // is_tracking not provided
        },
      };

      vi.mocked(apiClient.get).mockResolvedValueOnce(mockResponse);

      const { result } = renderHook(() => useResourceLocation('123'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      expect(result.current.data?.isTracking).toBe(false);
    });

    it('should handle null active_job explicitly', async () => {
      const mockResponse = {
        data: {
          has_location: true,
          latitude: 40.7128,
          longitude: -74.0060,
          is_tracking: true,
          active_job: null,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValueOnce(mockResponse);

      const { result } = renderHook(() => useResourceLocation('123'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      expect(result.current.data?.activeJob).toBeNull();
    });
  });

  describe('API calls', () => {
    it('should call the correct API endpoint with resourceId', async () => {
      const mockResponse = {
        data: {
          has_location: false,
          is_tracking: false,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValueOnce(mockResponse);

      const { result } = renderHook(() => useResourceLocation('789'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      expect(apiClient.get).toHaveBeenCalledWith('/resources/789/location/');
      expect(apiClient.get).toHaveBeenCalledTimes(1);
    });

    it('should not fetch when resourceId is null', () => {
      const { result } = renderHook(() => useResourceLocation(null), {
        wrapper: createWrapper(),
      });

      expect(result.current.isPending).toBe(true);
      expect(result.current.fetchStatus).toBe('idle');
      expect(apiClient.get).not.toHaveBeenCalled();
    });

    it('should not fetch when enabled is false', () => {
      const { result } = renderHook(
        () => useResourceLocation('123', { enabled: false }),
        {
          wrapper: createWrapper(),
        }
      );

      expect(result.current.isPending).toBe(true);
      expect(result.current.fetchStatus).toBe('idle');
      expect(apiClient.get).not.toHaveBeenCalled();
    });

    it('should fetch when enabled is true', async () => {
      const mockResponse = {
        data: {
          has_location: true,
          is_tracking: true,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValueOnce(mockResponse);

      const { result } = renderHook(
        () => useResourceLocation('123', { enabled: true }),
        {
          wrapper: createWrapper(),
        }
      );

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      expect(apiClient.get).toHaveBeenCalledWith('/resources/123/location/');
    });
  });

  describe('error handling', () => {
    it('should handle API errors', async () => {
      const mockError = new Error('Network error');
      vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);

      const { result } = renderHook(() => useResourceLocation('123'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isError).toBe(true));

      expect(result.current.error).toEqual(mockError);
      expect(result.current.data).toBeUndefined();
    });

    it('should handle 404 responses', async () => {
      const mockError = {
        response: {
          status: 404,
          data: { detail: 'Resource not found' },
        },
      };
      vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);

      const { result } = renderHook(() => useResourceLocation('999'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isError).toBe(true));

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('query configuration', () => {
    it('should use the correct query key', async () => {
      const mockResponse = {
        data: {
          has_location: false,
          is_tracking: false,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValueOnce(mockResponse);

      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
        },
      });

      const wrapper = ({ children }: { children: React.ReactNode }) =>
        React.createElement(QueryClientProvider, { client: queryClient }, children);

      const { result } = renderHook(() => useResourceLocation('123'), { wrapper });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      const cachedData = queryClient.getQueryData(['resourceLocation', '123']);
      expect(cachedData).toBeDefined();
      expect(cachedData).toEqual(result.current.data);
    });

    it('should not refetch automatically', async () => {
      const mockResponse = {
        data: {
          has_location: true,
          is_tracking: true,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const { result } = renderHook(() => useResourceLocation('123'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      // Wait a bit to ensure no automatic refetch
      await new Promise(resolve => setTimeout(resolve, 100));

      // Should only be called once (no refetchInterval)
      expect(apiClient.get).toHaveBeenCalledTimes(1);
    });
  });

  describe('optional fields', () => {
    it('should handle missing optional location fields', async () => {
      const mockResponse = {
        data: {
          has_location: true,
          latitude: 40.7128,
          longitude: -74.0060,
          is_tracking: true,
          // accuracy, heading, speed, timestamp not provided
        },
      };

      vi.mocked(apiClient.get).mockResolvedValueOnce(mockResponse);

      const { result } = renderHook(() => useResourceLocation('123'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      expect(result.current.data).toEqual({
        hasLocation: true,
        latitude: 40.7128,
        longitude: -74.0060,
        accuracy: undefined,
        heading: undefined,
        speed: undefined,
        timestamp: undefined,
        isTracking: true,
        activeJob: null,
        message: undefined,
      });
    });

    it('should handle message field when provided', async () => {
      const mockResponse = {
        data: {
          has_location: false,
          is_tracking: false,
          message: 'Resource has not started tracking yet',
        },
      };

      vi.mocked(apiClient.get).mockResolvedValueOnce(mockResponse);

      const { result } = renderHook(() => useResourceLocation('123'), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));

      expect(result.current.data?.message).toBe('Resource has not started tracking yet');
    });
  });
});

describe('useLiveResourceLocation', () => {
  let mockWebSocket: {
    close: ReturnType<typeof vi.fn>;
    send: ReturnType<typeof vi.fn>;
    addEventListener: ReturnType<typeof vi.fn>;
    removeEventListener: ReturnType<typeof vi.fn>;
    readyState: number;
    onopen: ((event: Event) => void) | null;
    onmessage: ((event: MessageEvent) => void) | null;
    onerror: ((event: Event) => void) | null;
    onclose: ((event: CloseEvent) => void) | null;
  };

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

    // Mock WebSocket
    mockWebSocket = {
      close: vi.fn(),
      send: vi.fn(),
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
      readyState: 1, // OPEN
      onopen: null,
      onmessage: null,
      onerror: null,
      onclose: null,
    };

    // Mock WebSocket constructor properly
    global.WebSocket = vi.fn(function(this: any) {
      return mockWebSocket;
    }) as any;
  });

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

  it('should not crash when rendered', () => {
    const { result } = renderHook(() => useLiveResourceLocation('123'), {
      wrapper: createWrapper(),
    });

    expect(result.current).toBeDefined();
    expect(result.current.refresh).toBeInstanceOf(Function);
  });

  it('should create WebSocket connection with correct URL', () => {
    renderHook(() => useLiveResourceLocation('456'), {
      wrapper: createWrapper(),
    });

    expect(global.WebSocket).toHaveBeenCalledWith(
      expect.stringContaining('/ws/resource-location/456/')
    );
  });

  it('should not connect when resourceId is null', () => {
    renderHook(() => useLiveResourceLocation(null), {
      wrapper: createWrapper(),
    });

    expect(global.WebSocket).not.toHaveBeenCalled();
  });

  it('should not connect when enabled is false', () => {
    renderHook(() => useLiveResourceLocation('123', { enabled: false }), {
      wrapper: createWrapper(),
    });

    expect(global.WebSocket).not.toHaveBeenCalled();
  });

  it('should close WebSocket on unmount', () => {
    const { unmount } = renderHook(() => useLiveResourceLocation('123'), {
      wrapper: createWrapper(),
    });

    unmount();

    expect(mockWebSocket.close).toHaveBeenCalledWith(1000, 'Component unmounting');
  });

  it('should return refresh function', () => {
    const { result } = renderHook(() => useLiveResourceLocation('123'), {
      wrapper: createWrapper(),
    });

    expect(result.current.refresh).toBeInstanceOf(Function);

    // Should not throw when called
    expect(() => result.current.refresh()).not.toThrow();
  });

  it('should handle location_update message type', () => {
    const queryClient = new QueryClient({
      defaultOptions: {
        queries: { retry: false },
      },
    });

    const wrapper = ({ children }: { children: React.ReactNode }) =>
      React.createElement(QueryClientProvider, { client: queryClient }, children);

    renderHook(() => useLiveResourceLocation('123'), { wrapper });

    // Simulate WebSocket message
    const mockMessage = {
      data: JSON.stringify({
        type: 'location_update',
        latitude: 40.7128,
        longitude: -74.0060,
        accuracy: 10,
        heading: 180,
        speed: 5.5,
        timestamp: '2025-12-07T12:00:00Z',
      }),
    };

    if (mockWebSocket.onmessage) {
      mockWebSocket.onmessage(mockMessage as MessageEvent);
    }

    // Verify query cache was updated
    const cachedData = queryClient.getQueryData(['resourceLocation', '123']);
    expect(cachedData).toBeDefined();
  });

  it('should handle tracking_stopped message type', () => {
    const queryClient = new QueryClient({
      defaultOptions: {
        queries: { retry: false },
      },
    });

    // Set initial data
    queryClient.setQueryData(['resourceLocation', '123'], {
      hasLocation: true,
      isTracking: true,
      latitude: 40.7128,
      longitude: -74.0060,
    });

    const wrapper = ({ children }: { children: React.ReactNode }) =>
      React.createElement(QueryClientProvider, { client: queryClient }, children);

    renderHook(() => useLiveResourceLocation('123'), { wrapper });

    // Simulate tracking stopped message
    const mockMessage = {
      data: JSON.stringify({
        type: 'tracking_stopped',
      }),
    };

    if (mockWebSocket.onmessage) {
      mockWebSocket.onmessage(mockMessage as MessageEvent);
    }

    // Verify isTracking was set to false
    const cachedData = queryClient.getQueryData<any>(['resourceLocation', '123']);
    expect(cachedData?.isTracking).toBe(false);
  });

  it('should handle malformed WebSocket messages gracefully', () => {
    const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

    renderHook(() => useLiveResourceLocation('123'), {
      wrapper: createWrapper(),
    });

    // Simulate malformed JSON
    const mockMessage = {
      data: 'invalid json{{{',
    };

    if (mockWebSocket.onmessage) {
      mockWebSocket.onmessage(mockMessage as MessageEvent);
    }

    // Should log error but not crash
    expect(consoleErrorSpy).toHaveBeenCalled();

    consoleErrorSpy.mockRestore();
  });
});
