import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { BluetoothLeTrackerMapper } from './bluetooth_le_tracker.mapper.js';
import type { IBluetoothLeDiscoveryRecord, IBluetoothLeManualEntry } from './bluetooth_le_tracker.types.js';
import { bluetoothLeTrackerDomain, bluetoothLeTrackerSourceType } from './bluetooth_le_tracker.types.js';

const bleTextHints = ['bluetooth', 'ble', 'beacon', 'ibeacon', 'tag', 'tracker'];

export class BluetoothLeAdvertisementMatcher implements IDiscoveryMatcher<IBluetoothLeDiscoveryRecord> {
  public id = 'bluetooth-le-tracker-bluetooth-match';
  public source = 'bluetooth' as const;
  public description = 'Recognize Bluetooth LE advertisements that can feed the BLE tracker.';

  public async matches(recordArg: IBluetoothLeDiscoveryRecord): Promise<IDiscoveryMatch> {
    const address = BluetoothLeTrackerMapper.normalizeAddress(recordArg.address || recordArg.mac || recordArg.macAddress || recordArg.haMac || recordArg.id);
    if (!address) {
      return { matched: false, confidence: 'low', reason: 'Bluetooth record does not include a trackable BLE MAC address.' };
    }
    const name = BluetoothLeTrackerMapper.cleanName(recordArg.name || recordArg.localName || recordArg.hostName);
    const hasBlePayload = Boolean(recordArg.serviceUuids?.length || recordArg.serviceUUIDs?.length || recordArg.serviceData || recordArg.manufacturerData || typeof recordArg.rssi === 'number' || recordArg.connectable !== undefined);
    return {
      matched: true,
      confidence: recordArg.sourceType === bluetoothLeTrackerSourceType ? 'certain' : hasBlePayload ? 'high' : 'medium',
      reason: hasBlePayload ? 'Bluetooth record contains BLE advertisement metadata.' : 'Bluetooth record contains a BLE address.',
      normalizedDeviceId: address,
      candidate: {
        source: 'bluetooth',
        integrationDomain: bluetoothLeTrackerDomain,
        id: address,
        name: name || BluetoothLeTrackerMapper.haMac(address),
        manufacturer: 'Bluetooth',
        model: 'Bluetooth LE device',
        macAddress: address,
        metadata: {
          address,
          haMac: BluetoothLeTrackerMapper.haMac(address),
          sourceType: bluetoothLeTrackerSourceType,
          rssi: recordArg.rssi,
          connectable: recordArg.connectable,
          serviceUuids: recordArg.serviceUuids || recordArg.serviceUUIDs,
          serviceData: recordArg.serviceData,
          manufacturerData: recordArg.manufacturerData,
          advertisement: recordArg,
        },
      },
    };
  }
}

export class BluetoothLeManualMatcher implements IDiscoveryMatcher<IBluetoothLeManualEntry> {
  public id = 'bluetooth-le-tracker-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual BLE tracker entries and known_devices-style BLE addresses.';

  public async matches(inputArg: IBluetoothLeManualEntry): Promise<IDiscoveryMatch> {
    const address = BluetoothLeTrackerMapper.normalizeAddress(inputArg.address || inputArg.mac || inputArg.macAddress || inputArg.haMac || inputArg.id)
      || this.firstDeviceAddress(inputArg);
    const metadata = inputArg.metadata || {};
    const matched = Boolean(address || metadata.bluetooth_le_tracker || metadata.bleTracker || inputArg.sourceType === bluetoothLeTrackerSourceType || inputArg.snapshot || inputArg.devices?.length || inputArg.knownDevices?.length || inputArg.trackedDevices?.length);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain BLE tracker setup hints.' };
    }
    return {
      matched: true,
      confidence: address ? 'high' : 'medium',
      reason: address ? 'Manual entry contains a trackable BLE address.' : 'Manual entry contains BLE tracker snapshot or device data.',
      normalizedDeviceId: inputArg.id || address,
      candidate: {
        source: 'manual',
        integrationDomain: bluetoothLeTrackerDomain,
        id: inputArg.id || address,
        name: inputArg.name || (address ? BluetoothLeTrackerMapper.haMac(address) : 'Bluetooth LE Tracker'),
        manufacturer: inputArg.manufacturer || 'Bluetooth',
        model: inputArg.model || 'Bluetooth LE tracker',
        macAddress: address,
        metadata: {
          ...metadata,
          address,
          haMac: BluetoothLeTrackerMapper.haMac(address),
          sourceType: bluetoothLeTrackerSourceType,
          track: inputArg.track,
          trackBattery: inputArg.trackBattery ?? inputArg.track_battery,
          knownDevices: inputArg.knownDevices,
          trackedDevices: inputArg.trackedDevices,
          devices: inputArg.devices,
          advertisements: inputArg.advertisements,
          snapshot: inputArg.snapshot,
        },
      },
    };
  }

  private firstDeviceAddress(inputArg: IBluetoothLeManualEntry): string | undefined {
    for (const device of [...(inputArg.knownDevices || []), ...(inputArg.trackedDevices || []), ...(inputArg.devices || []), ...(inputArg.snapshot?.devices || [])]) {
      const address = BluetoothLeTrackerMapper.normalizeAddress(device.address || device.mac || device.macAddress || device.haMac);
      if (address) {
        return address;
      }
    }
    return undefined;
  }
}

export class BluetoothLeTrackerCandidateValidator implements IDiscoveryValidator {
  public id = 'bluetooth-le-tracker-candidate-validator';
  public description = 'Validate BLE tracker discovery candidates.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const address = BluetoothLeTrackerMapper.normalizeAddress(candidateArg.macAddress || candidateArg.id || metadata.address || metadata.haMac);
    const text = [candidateArg.name, candidateArg.manufacturer, candidateArg.model].filter((valueArg): valueArg is string => typeof valueArg === 'string').join(' ').toLowerCase();
    const hasBleHint = candidateArg.source === 'bluetooth'
      || metadata.sourceType === bluetoothLeTrackerSourceType
      || metadata.bluetooth_le_tracker === true
      || metadata.bleTracker === true
      || bleTextHints.some((hintArg) => text.includes(hintArg));
    const matched = candidateArg.integrationDomain === bluetoothLeTrackerDomain || Boolean(address && hasBleHint);
    return {
      matched,
      confidence: matched && candidateArg.integrationDomain === bluetoothLeTrackerDomain && address ? 'certain' : matched && address ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has BLE tracker metadata.' : 'Candidate is not a Bluetooth LE tracker device.',
      normalizedDeviceId: address || candidateArg.id,
      candidate: matched ? {
        ...candidateArg,
        integrationDomain: bluetoothLeTrackerDomain,
        macAddress: address || candidateArg.macAddress,
        metadata: {
          ...metadata,
          address,
          haMac: BluetoothLeTrackerMapper.haMac(address),
          sourceType: bluetoothLeTrackerSourceType,
        },
      } : undefined,
    };
  }
}

export const createBluetoothLeTrackerDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: bluetoothLeTrackerDomain, displayName: 'Bluetooth LE Tracker' })
    .addMatcher(new BluetoothLeAdvertisementMatcher())
    .addMatcher(new BluetoothLeManualMatcher())
    .addValidator(new BluetoothLeTrackerCandidateValidator());
};
