import * as plugins from '../../plugins.js';
import { BaseIntegration } from '../../core/classes.baseintegration.js';
import type { IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
import { BluetoothLeTrackerClient } from './bluetooth_le_tracker.classes.client.js';
import { BluetoothLeTrackerConfigFlow } from './bluetooth_le_tracker.classes.configflow.js';
import { createBluetoothLeTrackerDiscoveryDescriptor } from './bluetooth_le_tracker.discovery.js';
import { BluetoothLeTrackerMapper } from './bluetooth_le_tracker.mapper.js';
import type { IBluetoothLeAdvertisement, IBluetoothLeTrackedDevice, IBluetoothLeTrackerCommand, IBluetoothLeTrackerConfig, IBluetoothLeTrackerSnapshot } from './bluetooth_le_tracker.types.js';
import { bluetoothLeTrackerDomain } from './bluetooth_le_tracker.types.js';

export class BluetoothLeTrackerIntegration extends BaseIntegration<IBluetoothLeTrackerConfig> {
  public readonly domain = bluetoothLeTrackerDomain;
  public readonly displayName = 'Bluetooth LE Tracker';
  public readonly status = 'read-only-runtime' as const;
  public readonly discoveryDescriptor = createBluetoothLeTrackerDiscoveryDescriptor();
  public readonly configFlow = new BluetoothLeTrackerConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/bluetooth_le_tracker',
    upstreamDomain: bluetoothLeTrackerDomain,
    iotClass: 'local_push',
    qualityScale: 'legacy',
    requirements: [] as string[],
    dependencies: ['bluetooth_adapters'],
    afterDependencies: [] as string[],
    codeowners: [] as string[],
    documentation: 'https://www.home-assistant.io/integrations/bluetooth_le_tracker',
    nativeBehavior: 'Maps injected BLE advertisement/snapshot data into device-tracker-like binary_sensor/sensor entities. Live Bluetooth adapter scanning is intentionally not implemented.',
  };

  public async setup(configArg: IBluetoothLeTrackerConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new BluetoothLeTrackerRuntime(new BluetoothLeTrackerClient(configArg));
  }

  public async destroy(): Promise<void> {}
}

export class HomeAssistantBluetoothLeTrackerIntegration extends BluetoothLeTrackerIntegration {}

class BluetoothLeTrackerRuntime implements IIntegrationRuntime {
  public domain = bluetoothLeTrackerDomain;

  constructor(private readonly client: BluetoothLeTrackerClient) {}

  public async devices(): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
    return BluetoothLeTrackerMapper.toDevices(await this.client.getSnapshot());
  }

  public async entities(): Promise<IIntegrationEntity[]> {
    return BluetoothLeTrackerMapper.toEntities(await this.client.getSnapshot());
  }

  public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void): Promise<() => Promise<void>> {
    const unsubscribe = this.client.onEvent((eventArg) => handlerArg({
      type: eventArg.type === 'unsupported_scan' ? 'error' : 'state_changed',
      integrationDomain: bluetoothLeTrackerDomain,
      deviceId: eventArg.deviceId,
      entityId: eventArg.entityId,
      data: eventArg,
      timestamp: eventArg.timestamp || Date.now(),
    }));
    await this.client.getSnapshot();
    return async () => unsubscribe();
  }

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const command = this.commandFromService(requestArg);
    if (!command) {
      return { success: false, error: `Unsupported Bluetooth LE Tracker service: ${requestArg.domain}.${requestArg.service}` };
    }
    const result = await this.client.sendCommand(command);
    return { success: result.success, error: result.error, data: result.data };
  }

  public async destroy(): Promise<void> {
    await this.client.destroy();
  }

  private commandFromService(requestArg: IServiceCallRequest): IBluetoothLeTrackerCommand | undefined {
    if (requestArg.domain !== bluetoothLeTrackerDomain || !['scan', 'scan_once', 'refresh', 'refresh_devices'].includes(requestArg.service)) {
      return undefined;
    }
    return {
      type: 'scan',
      service: requestArg.service,
      target: requestArg.target,
      snapshot: this.snapshotValue(requestArg.data?.snapshot),
      devices: this.arrayValue<IBluetoothLeTrackedDevice>(requestArg.data?.devices || requestArg.data?.trackedDevices || requestArg.data?.knownDevices),
      advertisements: this.arrayValue<IBluetoothLeAdvertisement>(requestArg.data?.advertisements || requestArg.data?.advertisement),
    };
  }

  private arrayValue<TValue>(valueArg: unknown): TValue[] | undefined {
    if (Array.isArray(valueArg)) {
      return valueArg as TValue[];
    }
    if (valueArg && typeof valueArg === 'object') {
      return [valueArg as TValue];
    }
    return undefined;
  }

  private snapshotValue(valueArg: unknown): IBluetoothLeTrackerSnapshot | undefined {
    return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as IBluetoothLeTrackerSnapshot : undefined;
  }
}
