import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, TEntityPlatform } from '../../core/types.js';
import type {
  IBluetoothLeAdvertisement,
  IBluetoothLeManualEntry,
  IBluetoothLeTrackedDevice,
  IBluetoothLeTrackerConfig,
  IBluetoothLeTrackerEvent,
  IBluetoothLeTrackerScannerState,
  IBluetoothLeTrackerSnapshot,
  TBluetoothLePresenceState,
} from './bluetooth_le_tracker.types.js';
import {
  bluetoothLeTrackerBlePrefix,
  bluetoothLeTrackerDefaultConsiderHomeSeconds,
  bluetoothLeTrackerDefaultMinSeenNew,
  bluetoothLeTrackerDefaultScanIntervalSeconds,
  bluetoothLeTrackerDefaultTrackBatteryIntervalSeconds,
  bluetoothLeTrackerDomain,
  bluetoothLeTrackerSourceType,
} from './bluetooth_le_tracker.types.js';

export class BluetoothLeTrackerMapper {
  public static toSnapshot(configArg: IBluetoothLeTrackerConfig, connectedArg?: boolean, eventsArg: IBluetoothLeTrackerEvent[] = []): IBluetoothLeTrackerSnapshot {
    const source = configArg.snapshot;
    const scanner = this.scannerState(configArg, source?.scanner);
    const advertisements = this.advertisementsFromConfig(configArg)
      .map((advertisementArg) => this.normalizeAdvertisement(advertisementArg))
      .filter((advertisementArg): advertisementArg is IBluetoothLeAdvertisement => Boolean(advertisementArg));
    const trackedDevices = new Map<string, IBluetoothLeTrackedDevice>();
    const blockedDevices = new Set<string>();

    const addDevice = (deviceArg: IBluetoothLeTrackedDevice | undefined): void => {
      const device = this.normalizeDevice(deviceArg);
      const address = device?.address;
      if (!device || !address) {
        return;
      }
      if (device.track === false || device.tracked === false) {
        blockedDevices.add(address);
        trackedDevices.delete(address);
        return;
      }
      const existing = trackedDevices.get(address);
      trackedDevices.set(address, this.mergeDevice(existing, device, scanner));
    };

    for (const device of this.devicesFromConfig(configArg)) {
      addDevice(device);
    }

    const advertisementCounts = new Map<string, number>();
    for (const advertisement of advertisements) {
      const address = advertisement.address;
      if (!address) {
        continue;
      }
      advertisementCounts.set(address, (advertisementCounts.get(address) || 0) + 1);
      if (blockedDevices.has(address)) {
        continue;
      }
      if (trackedDevices.has(address)) {
        addDevice(this.deviceFromAdvertisement(advertisement, advertisementCounts.get(address)));
      }
    }

    if (scanner.trackNewDevices) {
      for (const advertisement of advertisements) {
        const address = advertisement.address;
        if (!address || blockedDevices.has(address) || trackedDevices.has(address)) {
          continue;
        }
        const count = advertisementCounts.get(address) || 0;
        if (count >= scanner.minSeenNew) {
          addDevice(this.deviceFromAdvertisement(advertisement, count));
        }
      }
    }

    const devices = [...trackedDevices.values()].map((deviceArg) => this.withPresence(deviceArg, scanner));
    return {
      connected: connectedArg ?? source?.connected ?? this.hasStaticData(configArg),
      scanner,
      devices,
      advertisements,
      events: [...(source?.events || []), ...(configArg.events || []), ...eventsArg],
    };
  }

  public static toDevices(snapshotArg: IBluetoothLeTrackerSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    return snapshotArg.devices
      .map((deviceArg) => this.normalizeDevice(deviceArg))
      .filter((deviceArg): deviceArg is IBluetoothLeTrackedDevice => Boolean(deviceArg?.address && deviceArg.tracked !== false && deviceArg.track !== false))
      .map((deviceArg) => {
        const updatedAt = this.lastSeenIso(deviceArg) || new Date().toISOString();
        const presence = this.presenceState(deviceArg, snapshotArg.scanner);
        const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
          { id: 'presence', capability: 'sensor', name: 'Presence', readable: true, writable: false },
          { id: 'tracker_state', capability: 'sensor', name: 'Tracker state', readable: true, writable: false },
        ];
        const state: plugins.shxInterfaces.data.IDeviceState[] = [
          { featureId: 'presence', value: presence, updatedAt },
          { featureId: 'tracker_state', value: presence, updatedAt },
        ];
        if (typeof deviceArg.rssi === 'number') {
          features.push({ id: 'rssi', capability: 'sensor', name: 'RSSI', readable: true, writable: false, unit: 'dBm' });
          state.push({ featureId: 'rssi', value: deviceArg.rssi, updatedAt });
        }
        if (typeof deviceArg.battery === 'number' || deviceArg.trackBattery || deviceArg.track_battery) {
          features.push({ id: 'battery', capability: 'sensor', name: 'Battery', readable: true, writable: false, unit: '%' });
          state.push({ featureId: 'battery', value: typeof deviceArg.battery === 'number' ? deviceArg.battery : null, updatedAt });
        }
        const lastSeen = this.lastSeenIso(deviceArg);
        if (lastSeen) {
          features.push({ id: 'last_seen', capability: 'sensor', name: 'Last seen', readable: true, writable: false });
          state.push({ featureId: 'last_seen', value: lastSeen, updatedAt });
        }
        if (typeof deviceArg.advertisementCount === 'number') {
          features.push({ id: 'advertisement_count', capability: 'sensor', name: 'Advertisement count', readable: true, writable: false });
          state.push({ featureId: 'advertisement_count', value: deviceArg.advertisementCount, updatedAt });
        }
        return {
          id: this.deviceId(deviceArg),
          integrationDomain: bluetoothLeTrackerDomain,
          name: this.deviceName(deviceArg),
          protocol: 'unknown',
          manufacturer: deviceArg.manufacturer || 'Bluetooth',
          model: deviceArg.model || 'Bluetooth LE tracker',
          online: presence === 'home',
          features,
          state,
          metadata: {
            address: deviceArg.address,
            haMac: this.haMac(deviceArg.address),
            sourceType: bluetoothLeTrackerSourceType,
            connectable: deviceArg.connectable,
            serviceUuids: deviceArg.serviceUuids || deviceArg.serviceUUIDs,
            serviceData: deviceArg.serviceData,
            manufacturerData: deviceArg.manufacturerData,
            trackBattery: deviceArg.trackBattery ?? deviceArg.track_battery,
          },
        };
      });
  }

  public static toEntities(snapshotArg: IBluetoothLeTrackerSnapshot): IIntegrationEntity[] {
    const usedIds = new Map<string, number>();
    const entities: IIntegrationEntity[] = [];
    for (const device of snapshotArg.devices) {
      const normalizedDevice = this.normalizeDevice(device);
      if (!normalizedDevice?.address || normalizedDevice.track === false || normalizedDevice.tracked === false) {
        continue;
      }
      const deviceId = this.deviceId(normalizedDevice);
      const addressSlug = this.slug(normalizedDevice.address);
      const presence = this.presenceState(normalizedDevice, snapshotArg.scanner);
      const name = this.deviceName(normalizedDevice);
      entities.push(this.entity('binary_sensor', `${name} Presence`, deviceId, `bluetooth_le_tracker_presence_${addressSlug}`, presence === 'home' ? 'on' : 'off', usedIds, {
        deviceClass: 'presence',
        sourceType: bluetoothLeTrackerSourceType,
        address: normalizedDevice.address,
        haMac: this.haMac(normalizedDevice.address),
      }, true));
      entities.push(this.entity('sensor', `${name} Tracker State`, deviceId, `bluetooth_le_tracker_state_${addressSlug}`, presence, usedIds, {
        sourceType: bluetoothLeTrackerSourceType,
        address: normalizedDevice.address,
        haMac: this.haMac(normalizedDevice.address),
      }, true));
      if (typeof normalizedDevice.rssi === 'number') {
        entities.push(this.entity('sensor', `${name} RSSI`, deviceId, `bluetooth_le_tracker_rssi_${addressSlug}`, normalizedDevice.rssi, usedIds, { unit: 'dBm' }, presence === 'home'));
      }
      if (typeof normalizedDevice.battery === 'number' || normalizedDevice.trackBattery || normalizedDevice.track_battery) {
        entities.push(this.entity('sensor', `${name} Battery`, deviceId, `bluetooth_le_tracker_battery_${addressSlug}`, typeof normalizedDevice.battery === 'number' ? normalizedDevice.battery : null, usedIds, {
          deviceClass: 'battery',
          unit: '%',
        }, true));
      }
      const lastSeen = this.lastSeenIso(normalizedDevice);
      if (lastSeen) {
        entities.push(this.entity('sensor', `${name} Last Seen`, deviceId, `bluetooth_le_tracker_last_seen_${addressSlug}`, lastSeen, usedIds, { deviceClass: 'timestamp' }, true));
      }
      if (typeof normalizedDevice.advertisementCount === 'number') {
        entities.push(this.entity('sensor', `${name} Advertisement Count`, deviceId, `bluetooth_le_tracker_advertisement_count_${addressSlug}`, normalizedDevice.advertisementCount, usedIds, undefined, true));
      }
    }
    return entities;
  }

  public static normalizeAddress(valueArg: unknown): string | undefined {
    if (typeof valueArg !== 'string' || !valueArg.trim()) {
      return undefined;
    }
    const withoutPrefix = valueArg.trim().replace(new RegExp(`^${bluetoothLeTrackerBlePrefix}`, 'i'), '');
    const compact = withoutPrefix.toLowerCase().replace(/[^a-f0-9]/g, '');
    if (compact.length === 12) {
      return compact.match(/.{1,2}/g)?.join(':');
    }
    if (/^[a-f0-9]{2}([:-][a-f0-9]{2}){5}$/i.test(withoutPrefix)) {
      return withoutPrefix.toLowerCase().replace(/-/g, ':');
    }
    return undefined;
  }

  public static haMac(addressArg: unknown): string | undefined {
    const address = this.normalizeAddress(addressArg);
    return address ? `${bluetoothLeTrackerBlePrefix}${address.toUpperCase()}` : undefined;
  }

  public static cleanName(valueArg: unknown): string | undefined {
    if (typeof valueArg !== 'string') {
      return undefined;
    }
    const clean = valueArg.replace(/\x00/g, '').trim();
    return clean || undefined;
  }

  private static scannerState(configArg: IBluetoothLeTrackerConfig, sourceArg?: Partial<IBluetoothLeTrackerScannerState>): IBluetoothLeTrackerScannerState {
    return {
      scanIntervalSeconds: this.numberValue(configArg.scanIntervalSeconds, configArg.interval_seconds, sourceArg?.scanIntervalSeconds) ?? bluetoothLeTrackerDefaultScanIntervalSeconds,
      trackNewDevices: this.booleanValue(configArg.trackNewDevices, configArg.track_new_devices, sourceArg?.trackNewDevices) ?? true,
      trackBattery: this.booleanValue(configArg.trackBattery, configArg.track_battery, sourceArg?.trackBattery) ?? false,
      trackBatteryIntervalSeconds: this.numberValue(configArg.trackBatteryIntervalSeconds, configArg.track_battery_interval, sourceArg?.trackBatteryIntervalSeconds) ?? bluetoothLeTrackerDefaultTrackBatteryIntervalSeconds,
      considerHomeSeconds: this.numberValue(configArg.considerHomeSeconds, configArg.consider_home, sourceArg?.considerHomeSeconds) ?? bluetoothLeTrackerDefaultConsiderHomeSeconds,
      minSeenNew: this.numberValue(configArg.minSeenNew, sourceArg?.minSeenNew) ?? bluetoothLeTrackerDefaultMinSeenNew,
    };
  }

  private static devicesFromConfig(configArg: IBluetoothLeTrackerConfig): IBluetoothLeTrackedDevice[] {
    const entries = configArg.manualEntries || [];
    return [
      ...(configArg.snapshot?.devices || []),
      ...(configArg.knownDevices || []),
      ...(configArg.trackedDevices || []),
      ...(configArg.devices || []),
      ...entries.flatMap((entryArg) => this.devicesFromManualEntry(entryArg)),
    ];
  }

  private static advertisementsFromConfig(configArg: IBluetoothLeTrackerConfig): IBluetoothLeAdvertisement[] {
    const entries = configArg.manualEntries || [];
    return [
      ...(configArg.snapshot?.advertisements || []),
      ...(configArg.advertisements || []),
      ...entries.flatMap((entryArg) => [
        ...(entryArg.snapshot?.advertisements || []),
        ...(entryArg.advertisements || []),
      ]),
    ];
  }

  private static devicesFromManualEntry(entryArg: IBluetoothLeManualEntry): IBluetoothLeTrackedDevice[] {
    const nested = [
      ...(entryArg.snapshot?.devices || []),
      ...(entryArg.knownDevices || []),
      ...(entryArg.trackedDevices || []),
      ...(entryArg.devices || []),
    ];
    const ownAddress = this.normalizeAddress(entryArg.address || entryArg.mac || entryArg.macAddress || entryArg.haMac);
    return ownAddress ? [entryArg, ...nested] : nested;
  }

  private static normalizeAdvertisement(advertisementArg: IBluetoothLeAdvertisement | undefined): IBluetoothLeAdvertisement | undefined {
    const address = this.normalizeAddress(advertisementArg?.address || advertisementArg?.mac || advertisementArg?.macAddress || advertisementArg?.haMac);
    if (!advertisementArg || !address) {
      return undefined;
    }
    const lastSeen = advertisementArg.lastSeen ?? advertisementArg.last_seen ?? advertisementArg.time ?? Date.now();
    return {
      ...advertisementArg,
      address,
      macAddress: address,
      haMac: this.haMac(address),
      name: this.cleanName(advertisementArg.name || advertisementArg.localName || advertisementArg.hostName),
      serviceUuids: advertisementArg.serviceUuids || advertisementArg.serviceUUIDs,
      lastSeen,
      sourceType: bluetoothLeTrackerSourceType,
      metadata: {
        ...advertisementArg.metadata,
        source: advertisementArg.source,
      },
    };
  }

  private static normalizeDevice(deviceArg: IBluetoothLeTrackedDevice | undefined): IBluetoothLeTrackedDevice | undefined {
    const address = this.normalizeAddress(deviceArg?.address || deviceArg?.mac || deviceArg?.macAddress || deviceArg?.haMac);
    if (!deviceArg || !address) {
      return undefined;
    }
    return {
      ...deviceArg,
      address,
      macAddress: address,
      haMac: this.haMac(address),
      name: this.cleanName(deviceArg.name || deviceArg.hostName || deviceArg.hostname),
      serviceUuids: deviceArg.serviceUuids || deviceArg.serviceUUIDs,
      sourceType: bluetoothLeTrackerSourceType,
    };
  }

  private static deviceFromAdvertisement(advertisementArg: IBluetoothLeAdvertisement, countArg?: number): IBluetoothLeTrackedDevice {
    return {
      address: advertisementArg.address,
      name: advertisementArg.name || advertisementArg.localName || advertisementArg.hostName,
      track: true,
      tracked: true,
      battery: advertisementArg.battery,
      rssi: advertisementArg.rssi,
      txPower: advertisementArg.txPower,
      connectable: advertisementArg.connectable,
      serviceUuids: advertisementArg.serviceUuids || advertisementArg.serviceUUIDs,
      serviceData: advertisementArg.serviceData,
      manufacturerData: advertisementArg.manufacturerData,
      lastSeen: advertisementArg.lastSeen ?? advertisementArg.last_seen ?? advertisementArg.time,
      advertisementCount: countArg,
      raw: advertisementArg.raw,
      metadata: {
        ...advertisementArg.metadata,
        discoveredFromAdvertisement: true,
      },
    };
  }

  private static mergeDevice(existingArg: IBluetoothLeTrackedDevice | undefined, incomingArg: IBluetoothLeTrackedDevice, scannerArg: IBluetoothLeTrackerScannerState): IBluetoothLeTrackedDevice {
    if (!existingArg) {
      return {
        ...incomingArg,
        track: incomingArg.track ?? true,
        tracked: incomingArg.tracked ?? incomingArg.track ?? true,
        trackBattery: incomingArg.trackBattery ?? incomingArg.track_battery ?? scannerArg.trackBattery,
      };
    }
    const latest = this.newerDevice(existingArg, incomingArg);
    return {
      ...existingArg,
      ...incomingArg,
      name: incomingArg.name || existingArg.name,
      manufacturer: incomingArg.manufacturer || existingArg.manufacturer,
      model: incomingArg.model || existingArg.model,
      battery: incomingArg.battery ?? existingArg.battery,
      rssi: incomingArg.rssi ?? existingArg.rssi,
      txPower: incomingArg.txPower ?? existingArg.txPower,
      connectable: incomingArg.connectable ?? existingArg.connectable,
      serviceUuids: incomingArg.serviceUuids || existingArg.serviceUuids,
      serviceData: incomingArg.serviceData || existingArg.serviceData,
      manufacturerData: incomingArg.manufacturerData || existingArg.manufacturerData,
      lastSeen: latest.lastSeen ?? latest.last_seen,
      last_seen: latest.last_seen,
      firstSeen: existingArg.firstSeen ?? existingArg.first_seen ?? incomingArg.firstSeen ?? incomingArg.first_seen,
      advertisementCount: Math.max(existingArg.advertisementCount || 0, incomingArg.advertisementCount || 0) || undefined,
      track: incomingArg.track ?? existingArg.track ?? true,
      tracked: incomingArg.tracked ?? existingArg.tracked ?? true,
      trackBattery: incomingArg.trackBattery ?? incomingArg.track_battery ?? existingArg.trackBattery ?? existingArg.track_battery ?? scannerArg.trackBattery,
      metadata: {
        ...existingArg.metadata,
        ...incomingArg.metadata,
      },
    };
  }

  private static newerDevice(firstArg: IBluetoothLeTrackedDevice, secondArg: IBluetoothLeTrackedDevice): IBluetoothLeTrackedDevice {
    const firstTime = this.timestampMillis(firstArg.lastSeen ?? firstArg.last_seen);
    const secondTime = this.timestampMillis(secondArg.lastSeen ?? secondArg.last_seen);
    return (secondTime || 0) >= (firstTime || 0) ? secondArg : firstArg;
  }

  private static withPresence(deviceArg: IBluetoothLeTrackedDevice, scannerArg: IBluetoothLeTrackerScannerState): IBluetoothLeTrackedDevice {
    return {
      ...deviceArg,
      state: this.presenceState(deviceArg, scannerArg),
    };
  }

  private static presenceState(deviceArg: IBluetoothLeTrackedDevice, scannerArg: IBluetoothLeTrackerScannerState): TBluetoothLePresenceState {
    const explicit = typeof deviceArg.state === 'string' ? deviceArg.state.toLowerCase() : undefined;
    if (explicit === 'home' || explicit === 'on' || explicit === 'true') {
      return 'home';
    }
    if (explicit === 'not_home' || explicit === 'off' || explicit === 'false') {
      return 'not_home';
    }
    const lastSeen = this.timestampMillis(deviceArg.lastSeen ?? deviceArg.last_seen);
    if (!lastSeen) {
      return typeof deviceArg.rssi === 'number' || typeof deviceArg.battery === 'number' ? 'home' : 'not_home';
    }
    return Date.now() - lastSeen <= scannerArg.considerHomeSeconds * 1000 ? 'home' : 'not_home';
  }

  private static lastSeenIso(deviceArg: IBluetoothLeTrackedDevice): string | undefined {
    const millis = this.timestampMillis(deviceArg.lastSeen ?? deviceArg.last_seen);
    return millis ? new Date(millis).toISOString() : undefined;
  }

  private static timestampMillis(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg > 1_000_000_000_000 ? valueArg : valueArg > 1_000_000_000 ? valueArg * 1000 : valueArg;
    }
    if (typeof valueArg === 'string' && valueArg.trim()) {
      const parsed = Number(valueArg);
      if (Number.isFinite(parsed)) {
        return this.timestampMillis(parsed);
      }
      const millis = Date.parse(valueArg);
      return Number.isNaN(millis) ? undefined : millis;
    }
    return undefined;
  }

  private static entity(platformArg: TEntityPlatform, nameArg: string, deviceIdArg: string, uniqueIdArg: string, stateArg: unknown, usedIdsArg: Map<string, number>, attributesArg?: Record<string, unknown>, availableArg = true): IIntegrationEntity {
    const baseId = `${platformArg}.${this.slug(nameArg)}`;
    const current = usedIdsArg.get(baseId) || 0;
    usedIdsArg.set(baseId, current + 1);
    return {
      id: current ? `${baseId}_${current + 1}` : baseId,
      uniqueId: uniqueIdArg,
      integrationDomain: bluetoothLeTrackerDomain,
      deviceId: deviceIdArg,
      platform: platformArg,
      name: nameArg,
      state: stateArg,
      attributes: attributesArg,
      available: availableArg,
    };
  }

  private static deviceId(deviceArg: IBluetoothLeTrackedDevice): string {
    return `${bluetoothLeTrackerDomain}.device.${this.slug(deviceArg.address || deviceArg.macAddress || deviceArg.mac || 'unknown')}`;
  }

  private static deviceName(deviceArg: IBluetoothLeTrackedDevice): string {
    return this.cleanName(deviceArg.name || deviceArg.hostName || deviceArg.hostname) || this.haMac(deviceArg.address) || deviceArg.address || 'Bluetooth LE device';
  }

  private static hasStaticData(configArg: IBluetoothLeTrackerConfig): boolean {
    return Boolean(configArg.snapshot || configArg.advertisements?.length || configArg.devices?.length || configArg.knownDevices?.length || configArg.trackedDevices?.length || configArg.manualEntries?.length);
  }

  private static booleanValue(...valuesArg: unknown[]): boolean | undefined {
    for (const value of valuesArg) {
      if (typeof value === 'boolean') {
        return value;
      }
      if (typeof value === 'string') {
        if (['true', '1', 'yes', 'on'].includes(value.toLowerCase())) return true;
        if (['false', '0', 'no', 'off'].includes(value.toLowerCase())) return false;
      }
    }
    return undefined;
  }

  private static numberValue(...valuesArg: unknown[]): number | undefined {
    for (const value of valuesArg) {
      if (typeof value === 'number' && Number.isFinite(value)) {
        return value;
      }
      if (typeof value === 'string' && value.trim()) {
        const parsed = Number(value);
        if (Number.isFinite(parsed)) {
          return parsed;
        }
      }
    }
    return undefined;
  }

  private static slug(valueArg: string): string {
    return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'bluetooth_le_tracker';
  }
}
