/**
 * Tests for BLEScanner component
 */

import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { BLEScanner, SelectionMode } from '../BLEScanner';
import { WPDevice } from '@/services/ble/types';

// Mock the BLE context
const mockScanDevices = jest.fn();
const mockStopScan = jest.fn();
const mockClearError = jest.fn();

const mockBLEContext = {
  foundDevices: [] as WPDevice[],
  isScanning: false,
  connectedDevices: new Set<string>(),
  isBLEAvailable: true,
  error: null as string | null,
  permissionError: false,
  scanDevices: mockScanDevices,
  stopScan: mockStopScan,
  clearError: mockClearError,
};

jest.mock('@/contexts/BLEContext', () => ({
  useBLE: () => mockBLEContext,
}));

// Mock WiFiSignalIndicator to avoid native dependencies
jest.mock('@/components/WiFiSignalIndicator', () => ({
  WiFiSignalIndicator: () => null,
  getSignalStrengthLabel: (rssi: number) => {
    if (rssi >= -50) return 'Excellent';
    if (rssi >= -60) return 'Good';
    if (rssi >= -70) return 'Fair';
    return 'Weak';
  },
  getSignalStrengthColor: () => '#10B981',
}));

// Mock Linking
jest.mock('react-native/Libraries/Linking/Linking', () => ({
  openSettings: jest.fn(),
}));

const mockDevices: WPDevice[] = [
  {
    id: 'device-1',
    name: 'WP_497_81a14c',
    mac: '142B2F81A14C',
    rssi: -55,
    wellId: 497,
  },
  {
    id: 'device-2',
    name: 'WP_523_81aad4',
    mac: '142B2F81AAD4',
    rssi: -67,
    wellId: 523,
  },
  {
    id: 'device-3',
    name: 'WP_600_abc123',
    mac: '142B2FABC123',
    rssi: -75,
    wellId: 600,
  },
];

describe('BLEScanner', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    // Reset mock context
    mockBLEContext.foundDevices = [];
    mockBLEContext.isScanning = false;
    mockBLEContext.isBLEAvailable = true;
    mockBLEContext.error = null;
    mockBLEContext.permissionError = false;
    mockScanDevices.mockResolvedValue(undefined);
  });

  describe('Initial State', () => {
    it('renders scan button when no devices are found', () => {
      const { getByTestId } = render(<BLEScanner testID="ble-scanner" />);

      expect(getByTestId('scan-button')).toBeTruthy();
    });

    it('shows instructions when showInstructions is true', () => {
      const { getByText } = render(
        <BLEScanner showInstructions={true} />
      );

      expect(getByText('Scanning for Sensors')).toBeTruthy();
      expect(getByText('Make sure sensors are powered on')).toBeTruthy();
    });

    it('shows simulator warning when BLE is not available', () => {
      mockBLEContext.isBLEAvailable = false;

      const { getByText } = render(<BLEScanner />);

      expect(getByText('Simulator mode - showing mock sensors')).toBeTruthy();
    });
  });

  describe('Scanning', () => {
    it('calls scanDevices when scan button is pressed', async () => {
      const { getByTestId } = render(<BLEScanner />);

      fireEvent.press(getByTestId('scan-button'));

      await waitFor(() => {
        expect(mockScanDevices).toHaveBeenCalledTimes(1);
      });
    });

    it('shows scanning indicator when scanning', () => {
      mockBLEContext.isScanning = true;

      const { getByText, getByTestId } = render(<BLEScanner />);

      expect(getByText('Scanning for WP sensors...')).toBeTruthy();
      expect(getByTestId('stop-scan-button')).toBeTruthy();
    });

    it('calls stopScan when stop button is pressed', () => {
      mockBLEContext.isScanning = true;

      const { getByTestId } = render(<BLEScanner />);

      fireEvent.press(getByTestId('stop-scan-button'));

      expect(mockStopScan).toHaveBeenCalledTimes(1);
    });

    it('clears error before scanning', async () => {
      const { getByTestId } = render(<BLEScanner />);

      fireEvent.press(getByTestId('scan-button'));

      await waitFor(() => {
        expect(mockClearError).toHaveBeenCalled();
      });
    });
  });

  describe('Device List', () => {
    beforeEach(() => {
      mockBLEContext.foundDevices = mockDevices;
    });

    it('displays found devices', () => {
      const { getByText } = render(<BLEScanner />);

      expect(getByText('Found Sensors (3)')).toBeTruthy();
      expect(getByText('WP_497_81a14c')).toBeTruthy();
      expect(getByText('WP_523_81aad4')).toBeTruthy();
      expect(getByText('WP_600_abc123')).toBeTruthy();
    });

    it('shows Well ID for devices', () => {
      const { getByText } = render(<BLEScanner />);

      expect(getByText('Well ID: 497')).toBeTruthy();
      expect(getByText('Well ID: 523')).toBeTruthy();
    });

    it('shows rescan button when devices are displayed', () => {
      const { getByTestId } = render(<BLEScanner />);

      expect(getByTestId('rescan-button')).toBeTruthy();
    });

    it('rescans when rescan button is pressed', async () => {
      const { getByTestId } = render(<BLEScanner />);

      fireEvent.press(getByTestId('rescan-button'));

      await waitFor(() => {
        expect(mockScanDevices).toHaveBeenCalled();
      });
    });
  });

  describe('Multiple Selection Mode', () => {
    beforeEach(() => {
      mockBLEContext.foundDevices = mockDevices;
    });

    it('shows select all button in multiple mode', () => {
      const { getByTestId } = render(
        <BLEScanner selectionMode="multiple" />
      );

      expect(getByTestId('select-all-button')).toBeTruthy();
    });

    it('toggles device selection on press', () => {
      const onDevicesSelected = jest.fn();
      const { getByTestId } = render(
        <BLEScanner
          selectionMode="multiple"
          onDevicesSelected={onDevicesSelected}
        />
      );

      // Click first device
      fireEvent.press(getByTestId('device-item-device-1'));

      expect(onDevicesSelected).toHaveBeenCalled();
    });

    it('selects all devices when select all is pressed', () => {
      const onDevicesSelected = jest.fn();
      const { getByTestId, getByText } = render(
        <BLEScanner
          selectionMode="multiple"
          onDevicesSelected={onDevicesSelected}
        />
      );

      // Toggle from "Select All" -> all selected
      fireEvent.press(getByTestId('select-all-button'));

      // Now should show "Deselect All"
      expect(getByText('Deselect All')).toBeTruthy();
    });

    it('deselects all when deselect all is pressed', () => {
      const onDevicesSelected = jest.fn();
      const { getByTestId, getByText } = render(
        <BLEScanner
          selectionMode="multiple"
          onDevicesSelected={onDevicesSelected}
        />
      );

      // First select all
      fireEvent.press(getByTestId('select-all-button'));

      // Then deselect all
      fireEvent.press(getByTestId('select-all-button'));

      expect(getByText('Select All')).toBeTruthy();
    });
  });

  describe('Single Selection Mode', () => {
    beforeEach(() => {
      mockBLEContext.foundDevices = mockDevices;
    });

    it('does not show select all button in single mode', () => {
      const { queryByTestId } = render(
        <BLEScanner selectionMode="single" />
      );

      expect(queryByTestId('select-all-button')).toBeNull();
    });

    it('calls onDeviceSelected when a device is selected', () => {
      const onDeviceSelected = jest.fn();
      const { getByTestId } = render(
        <BLEScanner
          selectionMode="single"
          onDeviceSelected={onDeviceSelected}
        />
      );

      fireEvent.press(getByTestId('device-item-device-1'));

      expect(onDeviceSelected).toHaveBeenCalledWith(
        expect.objectContaining({
          id: 'device-1',
          name: 'WP_497_81a14c',
        })
      );
    });

    it('replaces selection when another device is selected', () => {
      const onDeviceSelected = jest.fn();
      const { getByTestId } = render(
        <BLEScanner
          selectionMode="single"
          onDeviceSelected={onDeviceSelected}
        />
      );

      fireEvent.press(getByTestId('device-item-device-1'));
      fireEvent.press(getByTestId('device-item-device-2'));

      // Last call should be with device-2
      expect(onDeviceSelected).toHaveBeenLastCalledWith(
        expect.objectContaining({
          id: 'device-2',
        })
      );
    });
  });

  describe('None Selection Mode', () => {
    beforeEach(() => {
      mockBLEContext.foundDevices = mockDevices;
    });

    it('does not show checkboxes in none mode', () => {
      const { queryByTestId } = render(
        <BLEScanner selectionMode="none" />
      );

      // Should not have select all button
      expect(queryByTestId('select-all-button')).toBeNull();
    });

    it('devices are not selectable in none mode', () => {
      const onDevicesSelected = jest.fn();
      const { getByTestId } = render(
        <BLEScanner
          selectionMode="none"
          onDevicesSelected={onDevicesSelected}
        />
      );

      fireEvent.press(getByTestId('device-item-device-1'));

      // Should be called with empty array (nothing selected)
      expect(onDevicesSelected).toHaveBeenCalledWith([]);
    });
  });

  describe('Disabled Devices', () => {
    beforeEach(() => {
      mockBLEContext.foundDevices = mockDevices;
    });

    it('shows disabled badge for disabled devices', () => {
      const { getByText } = render(
        <BLEScanner disabledDeviceIds={new Set(['device-1'])} />
      );

      expect(getByText('Added')).toBeTruthy();
    });

    it('does not allow selecting disabled devices', () => {
      const onDevicesSelected = jest.fn();
      const { getByTestId } = render(
        <BLEScanner
          selectionMode="multiple"
          disabledDeviceIds={new Set(['device-1'])}
          onDevicesSelected={onDevicesSelected}
        />
      );

      // Try to select disabled device
      fireEvent.press(getByTestId('device-item-device-1'));

      // Should not include disabled device in selection
      const lastCall = onDevicesSelected.mock.calls[onDevicesSelected.mock.calls.length - 1];
      expect(lastCall[0].find((d: WPDevice) => d.id === 'device-1')).toBeUndefined();
    });

    it('excludes disabled devices from select all', () => {
      const onDevicesSelected = jest.fn();
      const { getByTestId } = render(
        <BLEScanner
          selectionMode="multiple"
          disabledDeviceIds={new Set(['device-1'])}
          onDevicesSelected={onDevicesSelected}
        />
      );

      fireEvent.press(getByTestId('select-all-button'));

      // Should only have 2 devices (not including device-1)
      const lastCall = onDevicesSelected.mock.calls[onDevicesSelected.mock.calls.length - 1];
      expect(lastCall[0].length).toBe(2);
      expect(lastCall[0].find((d: WPDevice) => d.id === 'device-1')).toBeUndefined();
    });
  });

  describe('Device Filter', () => {
    beforeEach(() => {
      mockBLEContext.foundDevices = mockDevices;
    });

    it('applies custom filter to devices', () => {
      const filter = (device: WPDevice) => device.rssi > -60;

      const { getByText, queryByText } = render(
        <BLEScanner deviceFilter={filter} />
      );

      // Only device-1 has rssi > -60
      expect(getByText('WP_497_81a14c')).toBeTruthy();
      expect(queryByText('WP_523_81aad4')).toBeNull();
      expect(queryByText('WP_600_abc123')).toBeNull();
    });

    it('shows correct count with filter applied', () => {
      const filter = (device: WPDevice) => device.rssi > -70;

      const { getByText } = render(
        <BLEScanner deviceFilter={filter} />
      );

      expect(getByText('Found Sensors (2)')).toBeTruthy();
    });
  });

  describe('Empty State', () => {
    it('shows empty state after scan with no devices', () => {
      mockBLEContext.foundDevices = [];

      // Simulate scan completion
      const { getByTestId, rerender, getByText } = render(<BLEScanner />);

      // Trigger scan
      fireEvent.press(getByTestId('scan-button'));

      // Re-render with scan completed
      mockBLEContext.foundDevices = [];
      rerender(<BLEScanner />);

      // Should show retry button (which appears in empty state after scan)
      // Note: getByTestId will fail if element not present, so we check the retry button
      expect(getByTestId('retry-scan-button')).toBeTruthy();
    });

    it('shows custom empty state message', async () => {
      const customMessage = 'Custom empty message here';

      const { getByTestId, getByText, rerender } = render(
        <BLEScanner emptyStateMessage={customMessage} />
      );

      // Trigger scan
      fireEvent.press(getByTestId('scan-button'));

      // Re-render after scan
      rerender(<BLEScanner emptyStateMessage={customMessage} />);

      await waitFor(() => {
        expect(getByText(customMessage)).toBeTruthy();
      });
    });

    it('allows retry from empty state', async () => {
      const { getByTestId, rerender } = render(<BLEScanner />);

      // First scan
      fireEvent.press(getByTestId('scan-button'));
      rerender(<BLEScanner />);

      // Retry
      fireEvent.press(getByTestId('retry-scan-button'));

      await waitFor(() => {
        expect(mockScanDevices).toHaveBeenCalledTimes(2);
      });
    });
  });

  describe('Error Handling', () => {
    it('shows permission error with settings button', () => {
      mockBLEContext.error = 'Bluetooth permissions not granted';
      mockBLEContext.permissionError = true;

      const { getByText } = render(<BLEScanner />);

      expect(getByText('Bluetooth permissions not granted')).toBeTruthy();
      expect(getByText('Open Settings')).toBeTruthy();
    });

    it('does not show error banner for non-permission errors', () => {
      mockBLEContext.error = 'Some other error';
      mockBLEContext.permissionError = false;

      const { queryByText } = render(<BLEScanner />);

      // Permission error banner should not be shown
      expect(queryByText('Open Settings')).toBeNull();
    });
  });

  describe('Compact Mode', () => {
    beforeEach(() => {
      mockBLEContext.foundDevices = mockDevices;
    });

    it('renders in compact mode', () => {
      const { getByText } = render(<BLEScanner compact={true} />);

      expect(getByText('WP_497_81a14c')).toBeTruthy();
    });

    it('shows compact instructions', () => {
      mockBLEContext.foundDevices = [];

      const { getByText } = render(
        <BLEScanner compact={true} showInstructions={true} />
      );

      expect(getByText('Make sure sensors are powered on')).toBeTruthy();
    });
  });

  describe('Auto Scan', () => {
    it('starts scan automatically when autoScan is true', async () => {
      render(<BLEScanner autoScan={true} />);

      await waitFor(() => {
        expect(mockScanDevices).toHaveBeenCalledTimes(1);
      });
    });

    it('does not auto scan when autoScan is false', () => {
      render(<BLEScanner autoScan={false} />);

      expect(mockScanDevices).not.toHaveBeenCalled();
    });

    it('only auto scans once', async () => {
      const { rerender } = render(<BLEScanner autoScan={true} />);

      await waitFor(() => {
        expect(mockScanDevices).toHaveBeenCalledTimes(1);
      });

      // Re-render
      rerender(<BLEScanner autoScan={true} />);

      // Should still be 1
      expect(mockScanDevices).toHaveBeenCalledTimes(1);
    });
  });

  describe('Callbacks', () => {
    beforeEach(() => {
      mockBLEContext.foundDevices = mockDevices;
    });

    it('calls onDevicesSelected with selected devices', () => {
      const onDevicesSelected = jest.fn();

      const { getByTestId } = render(
        <BLEScanner
          selectionMode="multiple"
          onDevicesSelected={onDevicesSelected}
        />
      );

      fireEvent.press(getByTestId('device-item-device-1'));
      fireEvent.press(getByTestId('device-item-device-2'));

      expect(onDevicesSelected).toHaveBeenCalled();
      const lastCall = onDevicesSelected.mock.calls[onDevicesSelected.mock.calls.length - 1];
      expect(lastCall[0].length).toBe(2);
    });

    it('calls both onDeviceSelected and onDevicesSelected in single mode', () => {
      const onDeviceSelected = jest.fn();
      const onDevicesSelected = jest.fn();

      const { getByTestId } = render(
        <BLEScanner
          selectionMode="single"
          onDeviceSelected={onDeviceSelected}
          onDevicesSelected={onDevicesSelected}
        />
      );

      fireEvent.press(getByTestId('device-item-device-1'));

      expect(onDeviceSelected).toHaveBeenCalled();
      expect(onDevicesSelected).toHaveBeenCalled();
    });
  });
});
