import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IIntegrationEvent, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type {
  IAsuswrtActionDescriptor,
  IAsuswrtClientDevice,
  IAsuswrtCommand,
  IAsuswrtConfig,
  IAsuswrtEvent,
  IAsuswrtInterfaceStats,
  IAsuswrtManualEntry,
  IAsuswrtRouterInfo,
  IAsuswrtSensorMap,
  IAsuswrtSnapshot,
  TAsuswrtClientAction,
  TAsuswrtProtocol,
  TAsuswrtRouterAction,
} from './asuswrt.types.js';
import { asuswrtDefaultHttpPort, asuswrtDefaultHttpsPort, asuswrtDefaultSshPort, asuswrtDefaultTelnetPort, asuswrtDomain } from './asuswrt.types.js';

type TSensorDescriptor = {
  key: string;
  name: string;
  unit?: string;
  deviceClass?: string;
  stateClass?: string;
  entityCategory?: string;
  factor?: number;
};

const manufacturer = 'Asus';
const routerSensorDescriptors: TSensorDescriptor[] = [
  { key: 'sensor_connected_device', name: 'Devices Connected', unit: 'devices', stateClass: 'measurement' },
  { key: 'sensor_rx_rates', name: 'Download Speed', unit: 'Mbit/s', deviceClass: 'data_rate', stateClass: 'measurement', factor: 125000 },
  { key: 'sensor_tx_rates', name: 'Upload Speed', unit: 'Mbit/s', deviceClass: 'data_rate', stateClass: 'measurement', factor: 125000 },
  { key: 'sensor_rx_bytes', name: 'Download', unit: 'GB', deviceClass: 'data_size', stateClass: 'total_increasing', factor: 1000000000 },
  { key: 'sensor_tx_bytes', name: 'Upload', unit: 'GB', deviceClass: 'data_size', stateClass: 'total_increasing', factor: 1000000000 },
  { key: 'sensor_load_avg1', name: 'Average Load (1 min)', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: 'sensor_load_avg5', name: 'Average Load (5 min)', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: 'sensor_load_avg15', name: 'Average Load (15 min)', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: '2.4GHz', name: '2.4GHz Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: '5.0GHz', name: '5GHz Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: 'CPU', name: 'CPU Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: '5.0GHz_2', name: '5GHz Temperature (Radio 2)', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: '6.0GHz', name: '6GHz Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: 'mem_usage_perc', name: 'Memory Usage', unit: '%', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: 'mem_free', name: 'Memory Free', unit: 'MB', deviceClass: 'data_size', stateClass: 'measurement', entityCategory: 'diagnostic', factor: 1024 },
  { key: 'mem_used', name: 'Memory Used', unit: 'MB', deviceClass: 'data_size', stateClass: 'measurement', entityCategory: 'diagnostic', factor: 1024 },
  { key: 'sensor_last_boot', name: 'Last Boot', deviceClass: 'timestamp' },
  { key: 'sensor_uptime', name: 'Uptime', unit: 's', deviceClass: 'duration', stateClass: 'total', entityCategory: 'diagnostic' },
  { key: 'cpu_total_usage', name: 'CPU Usage', unit: '%', stateClass: 'measurement', entityCategory: 'diagnostic' },
  ...Array.from({ length: 8 }, (_valueArg, indexArg) => ({
    key: `cpu${indexArg + 1}_usage`,
    name: `CPU Core ${indexArg + 1} Usage`,
    unit: '%',
    stateClass: 'measurement',
    entityCategory: 'diagnostic',
  })),
];

export class AsuswrtMapper {
  public static toSnapshot(configArg: IAsuswrtConfig, connectedArg?: boolean, eventsArg: IAsuswrtEvent[] = []): IAsuswrtSnapshot {
    const source = configArg.snapshot;
    const manualSnapshots = (configArg.manualEntries || [])
      .map((entryArg) => entryArg.snapshot)
      .filter((snapshotArg): snapshotArg is IAsuswrtSnapshot => Boolean(snapshotArg));
    const manualData = this.mergeManualEntries(configArg.manualEntries || []);
    const router = this.routerInfo(configArg, source, manualSnapshots, manualData.router);
    const devices = this.uniqueClients([
      ...(source?.devices || []),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.devices || []),
      ...(configArg.devices || []),
      ...(configArg.clients || []),
      ...manualData.devices,
    ]);
    const interfaces = this.uniqueInterfaces([
      ...(source?.interfaces || []),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.interfaces || []),
      ...(configArg.interfaces || []),
      ...manualData.interfaces,
    ]);
    const sensors = this.sensorMap([
      source?.sensors,
      ...manualSnapshots.map((snapshotArg) => snapshotArg.sensors),
      configArg.sensors,
      manualData.sensors,
    ], devices.length, interfaces);
    const actions = this.uniqueActions([
      ...(source?.actions || []),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.actions || []),
      ...(configArg.actions || []),
      ...manualData.actions,
      ...this.actionsFromRouter(router),
      ...this.actionsFromClients(devices),
    ]);
    const hasManualData = Boolean(source || manualSnapshots.length || configArg.router || configArg.devices?.length || configArg.clients?.length || configArg.interfaces?.length || configArg.sensors || manualData.hasData);

    return {
      connected: connectedArg ?? configArg.connected ?? source?.connected ?? hasManualData,
      source: source?.source || (hasManualData ? 'manual' : 'runtime'),
      updatedAt: source?.updatedAt || new Date().toISOString(),
      router,
      devices,
      interfaces,
      sensors,
      actions,
      events: [...(source?.events || []), ...(configArg.events || []), ...eventsArg],
      error: source?.error,
      metadata: {
        ...source?.metadata,
        ...configArg.metadata,
        liveSshTelnetImplemented: false,
        commandExecutorConfigured: Boolean(configArg.commandExecutor || configArg.nativeClient?.executeCommand),
      },
    };
  }

  public static toDevices(snapshotArg: IAsuswrtSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [this.routerDevice(snapshotArg, updatedAt)];
    for (const client of snapshotArg.devices) {
      devices.push(this.clientDevice(client, snapshotArg, updatedAt));
    }
    return devices;
  }

  public static toEntities(snapshotArg: IAsuswrtSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    const usedIds = new Map<string, number>();
    const routerDeviceId = this.routerDeviceId(snapshotArg);
    entities.push(this.entity('binary_sensor', `${this.routerName(snapshotArg)} Connected`, routerDeviceId, `${this.uniqueBase(snapshotArg)}_connected`, snapshotArg.connected ? 'on' : 'off', usedIds, {
      deviceClass: 'connectivity',
      host: snapshotArg.router.host,
      port: snapshotArg.router.port,
      protocol: snapshotArg.router.protocol,
    }, true));

    for (const descriptor of routerSensorDescriptors) {
      const value = snapshotArg.sensors[descriptor.key];
      if (value === undefined) {
        continue;
      }
      entities.push(this.entity('sensor', `${this.routerName(snapshotArg)} ${descriptor.name}`, routerDeviceId, `${this.uniqueBase(snapshotArg)}_${this.slug(descriptor.key)}`, this.sensorValue(value, descriptor), usedIds, {
        nativeKey: descriptor.key,
        unit: descriptor.unit,
        deviceClass: descriptor.deviceClass,
        stateClass: descriptor.stateClass,
        entityCategory: descriptor.entityCategory,
      }, snapshotArg.connected));
    }

    for (const iface of snapshotArg.interfaces) {
      this.pushInterfaceEntities(entities, snapshotArg, iface, usedIds);
    }

    for (const client of snapshotArg.devices) {
      this.pushClientEntities(entities, snapshotArg, client, usedIds);
    }

    for (const action of this.snapshotActions(snapshotArg)) {
      const button = this.actionButton(snapshotArg, action, usedIds);
      if (button) {
        entities.push(button);
      }
    }

    return entities;
  }

  public static commandForService(snapshotArg: IAsuswrtSnapshot, requestArg: IServiceCallRequest): IAsuswrtCommand | undefined {
    const actions = this.snapshotActions(snapshotArg);
    const targetEntity = this.findTargetEntity(snapshotArg, requestArg);
    const serviceAction = this.actionFromService(requestArg.service);

    if (requestArg.domain === asuswrtDomain && requestArg.service === 'reboot') {
      const action = actions.find((actionArg) => actionArg.target === 'router' && actionArg.action === 'reboot');
      return action ? this.command(snapshotArg, requestArg, action) : undefined;
    }

    if (requestArg.domain === 'button' && requestArg.service === 'press' && targetEntity?.attributes?.nativeAction) {
      const targetAction = String(targetEntity.attributes.nativeAction);
      const action = actions.find((actionArg) => actionArg.entityId === targetEntity.id || actionArg.action === targetAction && (actionArg.target === 'router' || actionArg.mac === targetEntity.attributes?.mac));
      return action ? this.command(snapshotArg, requestArg, action, targetEntity) : undefined;
    }

    if (requestArg.domain !== asuswrtDomain || !serviceAction || serviceAction === 'reboot') {
      return undefined;
    }

    const mac = this.normalizeMac(this.stringValue(requestArg.data?.mac) || this.stringValue(requestArg.data?.macAddress) || this.stringValue(targetEntity?.attributes?.mac));
    const action = actions.find((actionArg) => actionArg.target === 'client' && actionArg.action === serviceAction && (!actionArg.mac || this.normalizeMac(actionArg.mac) === mac));
    return action && mac ? this.command(snapshotArg, requestArg, { ...action, mac }, targetEntity) : undefined;
  }

  public static toIntegrationEvent(eventArg: IAsuswrtEvent): IIntegrationEvent {
    return {
      type: eventArg.type === 'command_failed' || eventArg.type === 'error' ? 'error' : 'state_changed',
      integrationDomain: asuswrtDomain,
      deviceId: eventArg.deviceId,
      entityId: eventArg.entityId,
      data: eventArg,
      timestamp: eventArg.timestamp || Date.now(),
    };
  }

  public static normalizeMac(valueArg?: string): string | undefined {
    if (!valueArg) {
      return undefined;
    }
    const compact = valueArg.replace(/[^0-9a-f]/gi, '').toLowerCase();
    return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
  }

  public static defaultPort(protocolArg?: TAsuswrtProtocol): number {
    if (protocolArg === 'http') return asuswrtDefaultHttpPort;
    if (protocolArg === 'ssh') return asuswrtDefaultSshPort;
    if (protocolArg === 'telnet') return asuswrtDefaultTelnetPort;
    return asuswrtDefaultHttpsPort;
  }

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

  private static routerInfo(configArg: IAsuswrtConfig, sourceArg: IAsuswrtSnapshot | undefined, manualSnapshotsArg: IAsuswrtSnapshot[], manualRouterArg?: IAsuswrtRouterInfo): IAsuswrtRouterInfo {
    const manualRouter = manualRouterArg || manualSnapshotsArg.find((snapshotArg) => snapshotArg.router)?.router;
    const router = {
      ...sourceArg?.router,
      ...manualRouter,
      ...configArg.router,
    };
    const protocol = configArg.protocol || router.protocol || sourceArg?.router.protocol || 'https';
    const host = configArg.host || router.host || sourceArg?.router.host;
    const port = configArg.port || router.port || (host ? this.defaultPort(protocol) : undefined);
    const mac = this.normalizeMac(configArg.uniqueId || router.labelMac || router.macAddress || sourceArg?.router.labelMac || sourceArg?.router.macAddress);
    return {
      ...router,
      id: router.id || mac || (host ? `${host}:${port || this.defaultPort(protocol)}` : undefined) || configArg.name || 'asuswrt',
      host,
      port,
      name: configArg.name || router.name || host || 'ASUSWRT',
      protocol,
      mode: configArg.mode || router.mode || 'router',
      labelMac: mac || router.labelMac,
      macAddress: mac || router.macAddress,
      manufacturer: router.manufacturer || manufacturer,
      configurationUrl: router.configurationUrl || (host ? `${protocol === 'https' ? 'https' : 'http'}://${host}${port && !this.isDefaultWebPort(protocol, port) ? `:${port}` : ''}` : undefined),
    };
  }

  private static mergeManualEntries(entriesArg: IAsuswrtManualEntry[]): { router?: IAsuswrtRouterInfo; devices: IAsuswrtClientDevice[]; interfaces: IAsuswrtInterfaceStats[]; sensors?: IAsuswrtSensorMap; actions: IAsuswrtActionDescriptor[]; hasData: boolean } {
    const devices: IAsuswrtClientDevice[] = [];
    const interfaces: IAsuswrtInterfaceStats[] = [];
    const actions: IAsuswrtActionDescriptor[] = [];
    const sensors: IAsuswrtSensorMap = {};
    let router: IAsuswrtRouterInfo | undefined;
    let hasData = false;
    for (const entry of entriesArg) {
      if (entry.router) {
        router = { ...router, ...entry.router };
        hasData = true;
      } else if (!router && (entry.host || entry.name || entry.model || entry.macAddress)) {
        router = {
          id: entry.id || entry.macAddress || entry.host,
          host: entry.host,
          port: entry.port,
          protocol: entry.protocol,
          mode: entry.mode,
          name: entry.name,
          model: entry.model,
          macAddress: entry.macAddress,
          manufacturer: entry.manufacturer,
        };
        hasData = true;
      }
      devices.push(...(entry.devices || []), ...(entry.clients || []));
      interfaces.push(...(entry.interfaces || []));
      Object.assign(sensors, entry.sensors || {});
      actions.push(...(entry.actions || []));
      hasData = hasData || Boolean(entry.devices?.length || entry.clients?.length || entry.interfaces?.length || entry.sensors || entry.actions?.length);
    }
    return { router, devices, interfaces, sensors: Object.keys(sensors).length ? sensors : undefined, actions, hasData };
  }

  private static sensorMap(sourcesArg: Array<IAsuswrtSensorMap | undefined>, deviceCountArg: number, interfacesArg: IAsuswrtInterfaceStats[]): IAsuswrtSensorMap {
    const sensors: IAsuswrtSensorMap = {};
    for (const source of sourcesArg) {
      Object.assign(sensors, source || {});
    }
    if (sensors.sensor_connected_device === undefined && deviceCountArg > 0) {
      sensors.sensor_connected_device = deviceCountArg;
    }
    if (interfacesArg.length) {
      sensors.sensor_rx_bytes ??= this.sumInterfaces(interfacesArg, 'rxBytes', 'downloadBytes');
      sensors.sensor_tx_bytes ??= this.sumInterfaces(interfacesArg, 'txBytes', 'uploadBytes');
      sensors.sensor_rx_rates ??= this.sumInterfaces(interfacesArg, 'rxRate', 'downloadRate');
      sensors.sensor_tx_rates ??= this.sumInterfaces(interfacesArg, 'txRate', 'uploadRate');
    }
    return this.cleanAttributes(sensors) as IAsuswrtSensorMap;
  }

  private static sumInterfaces(interfacesArg: IAsuswrtInterfaceStats[], primaryKeyArg: keyof IAsuswrtInterfaceStats, fallbackKeyArg: keyof IAsuswrtInterfaceStats): number | undefined {
    let total = 0;
    let found = false;
    for (const iface of interfacesArg) {
      const value = this.numberValue(iface[primaryKeyArg]) ?? this.numberValue(iface[fallbackKeyArg]);
      if (value !== undefined) {
        total += value;
        found = true;
      }
    }
    return found ? total : undefined;
  }

  private static routerDevice(snapshotArg: IAsuswrtSnapshot, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    const sensors = snapshotArg.sensors;
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
      { id: 'connected_devices', capability: 'sensor', name: 'Connected Devices', readable: true, writable: false },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'connectivity', value: snapshotArg.connected ? 'online' : 'offline', updatedAt: updatedAtArg },
      { featureId: 'connected_devices', value: sensors.sensor_connected_device ?? snapshotArg.devices.filter((deviceArg) => deviceArg.connected !== false).length, updatedAt: updatedAtArg },
    ];
    this.addFeatureState(features, state, 'download_speed', 'Download Speed', this.sensorValue(sensors.sensor_rx_rates, routerSensorDescriptors[1]), updatedAtArg, 'Mbit/s');
    this.addFeatureState(features, state, 'upload_speed', 'Upload Speed', this.sensorValue(sensors.sensor_tx_rates, routerSensorDescriptors[2]), updatedAtArg, 'Mbit/s');
    this.addFeatureState(features, state, 'cpu_usage', 'CPU Usage', sensors.cpu_total_usage, updatedAtArg, '%');
    this.addFeatureState(features, state, 'memory_usage', 'Memory Usage', sensors.mem_usage_perc, updatedAtArg, '%');

    return {
      id: this.routerDeviceId(snapshotArg),
      integrationDomain: asuswrtDomain,
      name: this.routerName(snapshotArg),
      protocol: this.deviceProtocol(snapshotArg.router.protocol),
      manufacturer: snapshotArg.router.manufacturer || manufacturer,
      model: snapshotArg.router.model || 'Asus Router',
      online: snapshotArg.connected,
      features,
      state,
      metadata: this.cleanAttributes({
        host: snapshotArg.router.host,
        port: snapshotArg.router.port,
        protocol: snapshotArg.router.protocol,
        mode: snapshotArg.router.mode,
        macAddress: snapshotArg.router.macAddress || snapshotArg.router.labelMac,
        modelId: snapshotArg.router.modelId,
        serialNumber: snapshotArg.router.serialNumber,
        firmware: snapshotArg.router.firmware,
        configurationUrl: snapshotArg.router.configurationUrl,
        source: snapshotArg.source,
        liveSshTelnetImplemented: false,
      }),
    };
  }

  private static clientDevice(clientArg: IAsuswrtClientDevice, snapshotArg: IAsuswrtSnapshot, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    const name = this.clientName(clientArg);
    return {
      id: this.clientDeviceId(clientArg),
      integrationDomain: asuswrtDomain,
      name,
      protocol: 'unknown',
      manufacturer: clientArg.manufacturer || 'Unknown',
      model: clientArg.model || 'Network client',
      online: clientArg.connected !== false && snapshotArg.connected,
      features: [
        { id: 'presence', capability: 'sensor', name: 'Presence', readable: true, writable: false },
        { id: 'ip_address', capability: 'sensor', name: 'IP Address', readable: true, writable: false },
      ],
      state: [
        { featureId: 'presence', value: clientArg.connected !== false, updatedAt: updatedAtArg },
        { featureId: 'ip_address', value: clientArg.ipAddress || clientArg.ip || null, updatedAt: updatedAtArg },
      ],
      metadata: this.cleanAttributes({
        mac: this.clientMac(clientArg),
        ipAddress: clientArg.ipAddress || clientArg.ip,
        hostname: clientArg.hostname || clientArg.name,
        connectedTo: clientArg.connectedTo || clientArg.node || clientArg.interface,
        lastActivity: this.dateString(clientArg.lastActivity),
      }),
    };
  }

  private static pushInterfaceEntities(entitiesArg: IIntegrationEntity[], snapshotArg: IAsuswrtSnapshot, ifaceArg: IAsuswrtInterfaceStats, usedIdsArg: Map<string, number>): void {
    const deviceId = this.routerDeviceId(snapshotArg);
    const ifaceKey = this.slug(ifaceArg.id || ifaceArg.name);
    const ifaceName = ifaceArg.label || ifaceArg.name;
    const values: Array<[string, string, unknown, string | undefined, Record<string, unknown>]> = [
      ['rx_bytes', 'Download', this.bytesToGigabytes(ifaceArg.rxBytes ?? ifaceArg.downloadBytes), 'GB', { deviceClass: 'data_size', stateClass: 'total_increasing' }],
      ['tx_bytes', 'Upload', this.bytesToGigabytes(ifaceArg.txBytes ?? ifaceArg.uploadBytes), 'GB', { deviceClass: 'data_size', stateClass: 'total_increasing' }],
      ['rx_rate', 'Download Speed', this.bytesPerSecondToMegabits(ifaceArg.rxRate ?? ifaceArg.downloadRate), 'Mbit/s', { deviceClass: 'data_rate', stateClass: 'measurement' }],
      ['tx_rate', 'Upload Speed', this.bytesPerSecondToMegabits(ifaceArg.txRate ?? ifaceArg.uploadRate), 'Mbit/s', { deviceClass: 'data_rate', stateClass: 'measurement' }],
    ];
    for (const [key, name, value, unit, attrs] of values) {
      if (value === undefined) {
        continue;
      }
      entitiesArg.push(this.entity('sensor', `${this.routerName(snapshotArg)} ${ifaceName} ${name}`, deviceId, `${this.uniqueBase(snapshotArg)}_interface_${ifaceKey}_${key}`, value, usedIdsArg, {
        ...attrs,
        unit,
        interface: ifaceArg.name,
      }, snapshotArg.connected && ifaceArg.connected !== false));
    }
    if (ifaceArg.connected !== undefined) {
      entitiesArg.push(this.entity('binary_sensor', `${this.routerName(snapshotArg)} ${ifaceName} Link`, deviceId, `${this.uniqueBase(snapshotArg)}_interface_${ifaceKey}_link`, ifaceArg.connected ? 'on' : 'off', usedIdsArg, {
        deviceClass: 'connectivity',
        interface: ifaceArg.name,
      }, snapshotArg.connected));
    }
  }

  private static pushClientEntities(entitiesArg: IIntegrationEntity[], snapshotArg: IAsuswrtSnapshot, clientArg: IAsuswrtClientDevice, usedIdsArg: Map<string, number>): void {
    const mac = this.clientMac(clientArg);
    const deviceId = this.clientDeviceId(clientArg);
    entitiesArg.push(this.entity('binary_sensor', `${this.clientName(clientArg)} Connected`, deviceId, `${this.slug(mac || this.clientName(clientArg))}_connected`, clientArg.connected !== false ? 'on' : 'off', usedIdsArg, {
      deviceClass: 'connectivity',
      mac,
      ipAddress: clientArg.ipAddress || clientArg.ip,
      hostname: clientArg.hostname || clientArg.name,
      connectedTo: clientArg.connectedTo || clientArg.node || clientArg.interface,
      lastActivity: this.dateString(clientArg.lastActivity),
    }, snapshotArg.connected));
  }

  private static actionButton(snapshotArg: IAsuswrtSnapshot, actionArg: IAsuswrtActionDescriptor, usedIdsArg: Map<string, number>): IIntegrationEntity | undefined {
    if (actionArg.target === 'router' && actionArg.action === 'reboot') {
      return this.entity('button', `${this.routerName(snapshotArg)} Reboot`, this.routerDeviceId(snapshotArg), `${this.uniqueBase(snapshotArg)}_reboot`, 'available', usedIdsArg, {
        nativeAction: 'reboot',
        actionTarget: 'router',
        writable: true,
      }, snapshotArg.connected, actionArg.entityId);
    }
    if (actionArg.target !== 'client' || !actionArg.mac) {
      return undefined;
    }
    const client = snapshotArg.devices.find((clientArg) => this.clientMac(clientArg) === this.normalizeMac(actionArg.mac));
    if (!client) {
      return undefined;
    }
    return this.entity('button', `${this.clientName(client)} ${this.title(actionArg.action)}`, this.clientDeviceId(client), `${this.slug(actionArg.mac)}_${this.slug(actionArg.action)}`, 'available', usedIdsArg, {
      nativeAction: actionArg.action,
      actionTarget: 'client',
      mac: this.clientMac(client),
      writable: true,
    }, snapshotArg.connected && client.connected !== false, actionArg.entityId);
  }

  private static command(snapshotArg: IAsuswrtSnapshot, requestArg: IServiceCallRequest, actionArg: IAsuswrtActionDescriptor, entityArg?: IIntegrationEntity): IAsuswrtCommand {
    return {
      type: actionArg.target === 'router' ? 'router.reboot' : 'client.action',
      service: requestArg.service,
      action: actionArg.action,
      target: requestArg.target,
      protocol: snapshotArg.router.protocol,
      routerId: this.routerDeviceId(snapshotArg),
      mac: actionArg.mac ? this.normalizeMac(actionArg.mac) : undefined,
      entityId: entityArg?.id || actionArg.entityId || requestArg.target.entityId,
      deviceId: entityArg?.deviceId || actionArg.deviceId || requestArg.target.deviceId,
      payload: { ...(requestArg.data || {}), actionMetadata: actionArg.metadata },
    };
  }

  private static findTargetEntity(snapshotArg: IAsuswrtSnapshot, requestArg: IServiceCallRequest): IIntegrationEntity | undefined {
    const targetEntityId = requestArg.target.entityId;
    if (!targetEntityId) {
      return undefined;
    }
    return this.toEntities(snapshotArg).find((entityArg) => entityArg.id === targetEntityId || entityArg.uniqueId === targetEntityId);
  }

  private static actionFromService(serviceArg: string): TAsuswrtRouterAction | TAsuswrtClientAction | undefined {
    if (serviceArg === 'reboot') return 'reboot';
    if (serviceArg === 'reconnect_device' || serviceArg === 'reconnect_client') return 'reconnect';
    if (serviceArg === 'disconnect_device' || serviceArg === 'disconnect_client') return 'disconnect';
    if (serviceArg === 'block_device' || serviceArg === 'block_client') return 'block';
    if (serviceArg === 'unblock_device' || serviceArg === 'unblock_client') return 'unblock';
    return undefined;
  }

  private static actionsFromRouter(routerArg: IAsuswrtRouterInfo): IAsuswrtActionDescriptor[] {
    return (routerArg.actions || []).map((actionArg) => ({ target: 'router', action: actionArg }));
  }

  private static actionsFromClients(devicesArg: IAsuswrtClientDevice[]): IAsuswrtActionDescriptor[] {
    const actions: IAsuswrtActionDescriptor[] = [];
    for (const device of devicesArg) {
      const mac = this.clientMac(device);
      if (!mac) {
        continue;
      }
      for (const action of device.actions || []) {
        actions.push({ target: 'client', action, mac });
      }
    }
    return actions;
  }

  private static snapshotActions(snapshotArg: IAsuswrtSnapshot): IAsuswrtActionDescriptor[] {
    return this.uniqueActions([
      ...(snapshotArg.actions || []),
      ...this.actionsFromRouter(snapshotArg.router),
      ...this.actionsFromClients(snapshotArg.devices),
    ]);
  }

  private static uniqueClients(devicesArg: IAsuswrtClientDevice[]): IAsuswrtClientDevice[] {
    const seen = new Map<string, IAsuswrtClientDevice>();
    for (const device of devicesArg) {
      const key = this.clientMac(device) || device.id || device.ipAddress || device.ip || device.name;
      if (!key) {
        continue;
      }
      seen.set(key, { ...seen.get(key), ...device, mac: this.clientMac(device) || device.mac });
    }
    return [...seen.values()];
  }

  private static uniqueInterfaces(interfacesArg: IAsuswrtInterfaceStats[]): IAsuswrtInterfaceStats[] {
    const seen = new Map<string, IAsuswrtInterfaceStats>();
    for (const iface of interfacesArg) {
      const key = iface.id || iface.name;
      if (!key) {
        continue;
      }
      seen.set(key, { ...seen.get(key), ...iface });
    }
    return [...seen.values()];
  }

  private static uniqueActions(actionsArg: IAsuswrtActionDescriptor[]): IAsuswrtActionDescriptor[] {
    const seen = new Map<string, IAsuswrtActionDescriptor>();
    for (const action of actionsArg) {
      const mac = this.normalizeMac(action.mac);
      const key = [action.target, action.action, mac || action.entityId || action.deviceId || 'router'].join(':');
      seen.set(key, { ...action, mac });
    }
    return [...seen.values()];
  }

  private static routerDeviceId(snapshotArg: IAsuswrtSnapshot): string {
    return `asuswrt.router.${this.uniqueBase(snapshotArg)}`;
  }

  private static clientDeviceId(clientArg: IAsuswrtClientDevice): string {
    return `asuswrt.client.${this.slug(this.clientMac(clientArg) || clientArg.id || clientArg.ipAddress || clientArg.ip || this.clientName(clientArg))}`;
  }

  private static routerName(snapshotArg: IAsuswrtSnapshot): string {
    return snapshotArg.router.name || snapshotArg.router.host || 'ASUSWRT';
  }

  private static clientName(clientArg: IAsuswrtClientDevice): string {
    return clientArg.name || clientArg.hostname || clientArg.macAddress || clientArg.mac || clientArg.ipAddress || clientArg.ip || 'Unknown device';
  }

  private static clientMac(clientArg: IAsuswrtClientDevice): string | undefined {
    return this.normalizeMac(clientArg.macAddress || clientArg.mac);
  }

  private static uniqueBase(snapshotArg: IAsuswrtSnapshot): string {
    return this.slug(snapshotArg.router.labelMac || snapshotArg.router.macAddress || snapshotArg.router.serialNumber || snapshotArg.router.id || snapshotArg.router.host || this.routerName(snapshotArg));
  }

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

  private static sensorValue(valueArg: unknown, descriptorArg: TSensorDescriptor | undefined): unknown {
    if (valueArg === undefined || valueArg === null) {
      return valueArg;
    }
    if (descriptorArg?.factor && typeof valueArg === 'number') {
      return valueArg / descriptorArg.factor;
    }
    return valueArg;
  }

  private static addFeatureState(featuresArg: plugins.shxInterfaces.data.IDeviceFeature[], stateArg: plugins.shxInterfaces.data.IDeviceState[], idArg: string, nameArg: string, valueArg: unknown, updatedAtArg: string, unitArg?: string): void {
    if (valueArg === undefined) {
      return;
    }
    featuresArg.push({ id: idArg, capability: 'sensor', name: nameArg, readable: true, writable: false, unit: unitArg });
    stateArg.push({ featureId: idArg, value: this.deviceStateValue(valueArg), updatedAt: updatedAtArg });
  }

  private static bytesToGigabytes(valueArg: number | undefined): number | undefined {
    return valueArg === undefined ? undefined : valueArg / 1000000000;
  }

  private static bytesPerSecondToMegabits(valueArg: number | undefined): number | undefined {
    return valueArg === undefined ? undefined : valueArg / 125000;
  }

  private static deviceStateValue(valueArg: unknown): plugins.shxInterfaces.data.TDeviceStateValue {
    if (typeof valueArg === 'string' || typeof valueArg === 'number' || typeof valueArg === 'boolean' || valueArg === null) {
      return valueArg;
    }
    if (valueArg && typeof valueArg === 'object') {
      return valueArg as Record<string, unknown>;
    }
    return null;
  }

  private static deviceProtocol(protocolArg?: TAsuswrtProtocol): plugins.shxInterfaces.data.TDeviceProtocol {
    if (protocolArg === 'http' || protocolArg === 'https') {
      return 'http';
    }
    return 'unknown';
  }

  private static cleanAttributes(attributesArg: Record<string, unknown>): Record<string, unknown> {
    return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  }

  private static stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
  }

  private static numberValue(valueArg: unknown): number | undefined {
    return typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : undefined;
  }

  private static dateString(valueArg: IAsuswrtClientDevice['lastActivity']): string | undefined {
    if (valueArg instanceof Date) {
      return valueArg.toISOString();
    }
    if (typeof valueArg === 'number') {
      return new Date(valueArg).toISOString();
    }
    return typeof valueArg === 'string' ? valueArg : undefined;
  }

  private static title(valueArg: string): string {
    return valueArg.replace(/_/g, ' ').replace(/\b\w/g, (matchArg) => matchArg.toUpperCase());
  }

  private static isDefaultWebPort(protocolArg: TAsuswrtProtocol, portArg: number): boolean {
    return (protocolArg === 'http' && portArg === asuswrtDefaultHttpPort) || (protocolArg === 'https' && portArg === 443);
  }
}
