import { PlatformAccessory, Logger } from 'homebridge';
import { BaseAccessory } from '../accessories/base.accessory';
import { TSVESyncPlatform } from '../platform';
import { DeviceCapabilities } from '../types/device.types';
import { PluginLogger } from '../utils/logger';
import { RetryManager } from '../utils/retry';

jest.mock('../utils/logger');
jest.mock('../utils/retry');

// Create a concrete test class since BaseAccessory is abstract
class TestAccessory extends BaseAccessory {
  protected setupService(): void {
    this.service = new this.platform.Service.Switch(this.accessory.displayName);
  }
  
  protected async updateDeviceSpecificStates(_details: any): Promise<void> {
    // Test implementation
  }
  
  protected getDeviceCapabilities(): DeviceCapabilities {
    return {
      hasBrightness: false,
      hasColorTemp: false,
      hasColor: false,
      hasSpeed: false,
      hasHumidity: false,
      hasAirQuality: false,
      hasWaterLevel: false,
      hasChildLock: false,
      hasSwingMode: false,
    };
  }

  // Expose handleDeviceError for testing
  public async testHandleDeviceError(message: string, error: Error | any): Promise<void> {
    return (this as any).handleDeviceError(message, error);
  }
}

// Add test factory before the describe block
const createMockDevice = (overrides = {}) => ({
  deviceName: 'Test Device',
  uuid: '12345',
  deviceType: 'outlet',
  getDetails: jest.fn(),
  ...overrides
});

describe('BaseAccessory', () => {
  let mockPlatform: jest.Mocked<TSVESyncPlatform>;
  let mockAccessory: jest.Mocked<PlatformAccessory>;
  let mockDevice: any;
  let mockService: jest.Mock;
  let mockInfoService: jest.Mock;
  let accessory: TestAccessory;
  let mockLogger: jest.Mocked<Logger>;
  let mockPluginLogger: jest.Mocked<PluginLogger>;

  beforeAll(() => {
    jest.setTimeout(30000);
  });

  beforeEach(() => {
    jest.useFakeTimers({ advanceTimers: true });

    // Setup logger mock
    mockLogger = {
      debug: jest.fn(),
      info: jest.fn(),
      warn: jest.fn(),
      error: jest.fn(),
      log: jest.fn(),
      prefix: undefined
    } as jest.Mocked<Logger>;

    // Setup PluginLogger mock
    const MockPluginLogger = jest.mocked(PluginLogger);
    mockPluginLogger = {
      debug: jest.fn(),
      info: jest.fn(),
      warn: jest.fn(),
      error: jest.fn(),
      stateChange: jest.fn(),
      operationStart: jest.fn(),
      operationEnd: jest.fn(),
      pollingEvent: jest.fn(),
      log: mockLogger,
      debugMode: true,
      formatMessage: jest.fn((message: string) => message),
    } as unknown as jest.Mocked<PluginLogger>;

    MockPluginLogger.mockImplementation(() => mockPluginLogger);

    // Setup service mocks
    mockService = jest.fn().mockImplementation(() => ({
      getCharacteristic: jest.fn().mockReturnValue({
        onSet: jest.fn(),
        onGet: jest.fn(),
        updateValue: jest.fn(),
      }),
      setCharacteristic: jest.fn().mockReturnThis(),
    }));

    mockInfoService = jest.fn().mockImplementation(() => ({
      setCharacteristic: jest.fn().mockReturnThis(),
    }));

    // Setup platform mock
    mockPlatform = {
      log: mockLogger,
      Service: {
        Switch: mockService,
        AccessoryInformation: mockInfoService,
      },
      Characteristic: {
        Manufacturer: 'Manufacturer',
        Model: 'Model',
        SerialNumber: 'SerialNumber',
        FirmwareRevision: 'FirmwareRevision',
      },
      api: {
        updatePlatformAccessories: jest.fn().mockImplementation((accessories: PlatformAccessory[]) => {}),
      },
      config: {
        platform: 'TSVESync',
        name: 'TSVESync',
        username: 'test@example.com',
        password: 'password123',
        debug: true,
      },
      discoverDevices: jest.fn(),
    } as unknown as jest.Mocked<TSVESyncPlatform>;

    // Setup accessory mock
    const mockGetService = jest.fn().mockImplementation((service) => {
      if (service === mockInfoService) {
        return {
          setCharacteristic: jest.fn().mockReturnThis(),
        };
      }
      return null;
    });

    mockAccessory = {
      displayName: 'Test Device',
      context: { device: { details: {} } },
      getService: mockGetService,
      addService: jest.fn(),
    } as unknown as jest.Mocked<PlatformAccessory>;

    // Setup device mock
    mockDevice = {
      deviceName: 'Test Device',
      uuid: '12345',
      deviceType: 'outlet',
      getDetails: jest.fn(),
    };

    // Create accessory instance
    accessory = new TestAccessory(mockPlatform, mockAccessory, mockDevice);

    // Mock PluginLogger
    (accessory as any).logger = mockPluginLogger;
  });

  afterEach(() => {
    jest.clearAllTimers();
    jest.useRealTimers();
    jest.clearAllMocks();
  });

  describe('error handling', () => {
    it('should handle device not found error', async () => {
      const error = {
        error: {
          code: 4041008,
          msg: 'Device not found',
        },
      };

      await accessory.testHandleDeviceError('test operation', error);
      expect(mockPluginLogger.warn).toHaveBeenCalledWith(
        expect.stringContaining('Device not found'),
        expect.any(Object),
        expect.any(Error)
      );
    });

    it('should handle network errors', async () => {
      const error = {
        code: 'ECONNRESET',
        message: 'Connection reset',
      };

      await accessory.testHandleDeviceError('test operation', error);
      expect(mockPluginLogger.warn).toHaveBeenCalledWith(
        expect.stringContaining('Network error'),
        expect.any(Object),
        expect.any(Error)
      );
    });

    it('should handle API timeout errors', async () => {
      const error = {
        code: 'ETIMEDOUT',
        message: 'Request timed out',
      };

      await accessory.testHandleDeviceError('test operation', error);
      expect(mockPluginLogger.warn).toHaveBeenCalledWith(
        expect.stringContaining('Network error'),
        expect.any(Object),
        expect.any(Error)
      );
    });

    it('should handle invalid response format', async () => {
      const error = {
        message: 'Invalid response format',
      };

      await accessory.testHandleDeviceError('test operation', error);
      expect(mockPluginLogger.error).toHaveBeenCalledWith(
        expect.stringContaining('test operation'),
        expect.any(Object),
        expect.any(Error)
      );
    });
  });

  describe('device state management', () => {
    it('syncDeviceState calls updateDeviceSpecificStates', async () => {
      const updateSpy = jest.spyOn(accessory as any, 'updateDeviceSpecificStates');
      await accessory.syncDeviceState();
      expect(updateSpy).toHaveBeenCalledWith(mockDevice);
    });

    it('syncDeviceState swallows update errors and logs', async () => {
      jest.spyOn(accessory as any, 'updateDeviceSpecificStates').mockRejectedValueOnce(new Error('Boom'));
      await accessory.syncDeviceState();

      expect(mockPluginLogger.error).toHaveBeenCalledWith(
        expect.stringContaining('Failed to sync device state'),
        expect.any(Object),
        expect.any(Error)
      );
    });
  });

  describe('utility methods', () => {
    it('should convert air quality values correctly', () => {
      expect((accessory as any).convertAirQualityToHomeKit(10)).toBe(1);  // EXCELLENT
      expect((accessory as any).convertAirQualityToHomeKit(30)).toBe(2);  // GOOD
      expect((accessory as any).convertAirQualityToHomeKit(50)).toBe(3);  // FAIR
      expect((accessory as any).convertAirQualityToHomeKit(100)).toBe(4); // INFERIOR
      expect((accessory as any).convertAirQualityToHomeKit(200)).toBe(4); // INFERIOR
    });

    it('should persist device state', async () => {
      await (accessory as any).persistDeviceState('testKey', 'testValue');

      expect(mockPlatform.api.updatePlatformAccessories).toHaveBeenCalledWith([mockAccessory]);
      expect(mockAccessory.context.device.details.testKey).toBe('testValue');
    });
  });
}); 
