/**
 * BLE Context Cleanup Tests
 * Tests for BLE scanning cleanup when screens lose focus
 */

import React from 'react';
import { renderHook, act, waitFor } from '@testing-library/react-native';
import { BLEProvider, useBLE } from '@/contexts/BLEContext';
import * as bleModule from '@/services/ble';

// Mock the BLE service
jest.mock('@/services/ble', () => ({
  bleManager: {
    scanDevices: jest.fn(),
    stopScan: jest.fn(),
    connectDevice: jest.fn(),
    disconnectDevice: jest.fn(),
    getWiFiList: jest.fn(),
    setWiFi: jest.fn(),
    getCurrentWiFi: jest.fn(),
    rebootDevice: jest.fn(),
    cleanup: jest.fn(),
  },
  isBLEAvailable: true,
}));

describe('BLEContext cleanup behavior', () => {
  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <BLEProvider>{children}</BLEProvider>
  );

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

  it('should stop scan when cleanupBLE is called while scanning', async () => {
    const mockScanDevices = jest.fn().mockResolvedValue([
      { id: 'device-1', name: 'WP_497_81a14c', mac: '81A14C', rssi: -55, wellId: 497 },
    ]);
    const mockStopScan = jest.fn();

    (bleModule.bleManager.scanDevices as jest.Mock) = mockScanDevices;
    (bleModule.bleManager.stopScan as jest.Mock) = mockStopScan;

    const { result } = renderHook(() => useBLE(), { wrapper });

    // Start scanning
    act(() => {
      result.current.scanDevices();
    });

    expect(result.current.isScanning).toBe(true);

    // Cleanup while scanning
    await act(async () => {
      await result.current.cleanupBLE();
    });

    expect(mockStopScan).toHaveBeenCalled();
    expect(result.current.isScanning).toBe(false);
  });

  it('should clear found devices when cleanupBLE is called', async () => {
    const mockScanDevices = jest.fn().mockResolvedValue([
      { id: 'device-1', name: 'WP_497_81a14c', mac: '81A14C', rssi: -55, wellId: 497 },
      { id: 'device-2', name: 'WP_498_82b25d', mac: '82B25D', rssi: -60, wellId: 498 },
    ]);

    (bleModule.bleManager.scanDevices as jest.Mock) = mockScanDevices;

    const { result } = renderHook(() => useBLE(), { wrapper });

    // Scan and find devices
    await act(async () => {
      await result.current.scanDevices();
    });

    expect(result.current.foundDevices).toHaveLength(2);

    // Cleanup
    await act(async () => {
      await result.current.cleanupBLE();
    });

    expect(result.current.foundDevices).toHaveLength(0);
  });

  it('should clear connected devices when cleanupBLE is called', async () => {
    const mockConnectDevice = jest.fn().mockResolvedValue(true);
    const mockCleanup = jest.fn().mockResolvedValue(undefined);

    (bleModule.bleManager.connectDevice as jest.Mock) = mockConnectDevice;
    (bleModule.bleManager.cleanup as jest.Mock) = mockCleanup;

    const { result } = renderHook(() => useBLE(), { wrapper });

    // Connect to a device
    await act(async () => {
      await result.current.connectDevice('device-1');
    });

    expect(result.current.connectedDevices.size).toBe(1);

    // Cleanup
    await act(async () => {
      await result.current.cleanupBLE();
    });

    expect(mockCleanup).toHaveBeenCalled();
    expect(result.current.connectedDevices.size).toBe(0);
  });

  it('should clear errors when cleanupBLE is called', async () => {
    const mockScanDevices = jest.fn().mockRejectedValue(new Error('Scan failed'));

    (bleModule.bleManager.scanDevices as jest.Mock) = mockScanDevices;

    const { result } = renderHook(() => useBLE(), { wrapper });

    // Trigger an error
    await act(async () => {
      try {
        await result.current.scanDevices();
      } catch {
        // Expected
      }
    });

    expect(result.current.error).toBeTruthy();

    // Cleanup should clear errors
    await act(async () => {
      await result.current.cleanupBLE();
    });

    expect(result.current.error).toBeNull();
  });

  it('should not throw if cleanup fails', async () => {
    const mockCleanup = jest.fn().mockRejectedValue(new Error('Cleanup failed'));

    (bleModule.bleManager.cleanup as jest.Mock) = mockCleanup;

    const { result } = renderHook(() => useBLE(), { wrapper });

    // Cleanup should not throw even if bleManager.cleanup fails
    await expect(
      act(async () => {
        await result.current.cleanupBLE();
      })
    ).resolves.not.toThrow();
  });

  it('should stop scan before calling bleManager.cleanup', async () => {
    const callOrder: string[] = [];

    const mockStopScan = jest.fn(() => {
      callOrder.push('stopScan');
    });
    const mockCleanup = jest.fn().mockResolvedValue(undefined).mockImplementation(() => {
      callOrder.push('cleanup');
    });

    (bleModule.bleManager.stopScan as jest.Mock) = mockStopScan;
    (bleModule.bleManager.cleanup as jest.Mock) = mockCleanup;

    const { result } = renderHook(() => useBLE(), { wrapper });

    // Start scanning
    act(() => {
      (result.current as any).setIsScanning?.(true);
    });

    // Trigger cleanup
    await act(async () => {
      await result.current.cleanupBLE();
    });

    // stopScan should be called before cleanup
    expect(callOrder).toEqual(['stopScan', 'cleanup']);
  });
});
