import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type { IZhaClientCommand, IZhaConfig, IZhaDevice, IZhaEntityDescriptor, IZhaSnapshot, TZhaEntityPlatform } from './zha.types.js';

export class ZhaMapper {
  public static toSnapshot(configArg: IZhaConfig, connectedArg?: boolean, eventsArg: IZhaSnapshot['events'] = []): IZhaSnapshot {
    const source = configArg.snapshot;
    return {
      connected: connectedArg ?? source?.connected ?? false,
      radio: configArg.radio || source?.radio || {
        path: configArg.usbPath || configArg.usb_path || configArg.device?.path,
        radioType: configArg.radioType || configArg.radio_type,
        baudrate: configArg.device?.baudrate,
        flowControl: configArg.device?.flowControl || configArg.device?.flow_control,
      },
      network: configArg.network || source?.network,
      coordinator: configArg.coordinator || source?.coordinator,
      devices: [...(source?.devices || []), ...(configArg.devices || [])],
      entities: [...(source?.entities || []), ...(configArg.entities || [])],
      groups: [...(source?.groups || []), ...(configArg.groups || [])],
      events: [...(source?.events || []), ...(configArg.events || []), ...eventsArg],
    };
  }

  public static toDevices(snapshotArg: IZhaSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = new Date().toISOString();
    const devices = new Map<string, plugins.shxInterfaces.data.IDeviceDefinition>();
    const coordinatorId = this.coordinatorDeviceId(snapshotArg);
    devices.set(coordinatorId, {
      id: coordinatorId,
      integrationDomain: 'zha',
      name: 'ZHA coordinator',
      protocol: 'zigbee',
      manufacturer: snapshotArg.coordinator?.manufacturer || 'Zigbee',
      model: snapshotArg.coordinator?.model || String(snapshotArg.radio?.radioType || 'Coordinator'),
      online: snapshotArg.connected || Boolean(snapshotArg.devices.length || snapshotArg.entities.length),
      features: [
        { id: 'connection', capability: 'sensor', name: 'Connection', readable: true, writable: false },
        { id: 'device_count', capability: 'sensor', name: 'Device count', readable: true, writable: false },
      ],
      state: [
        { featureId: 'connection', value: snapshotArg.connected ? 'connected' : 'configured', updatedAt },
        { featureId: 'device_count', value: snapshotArg.devices.length, updatedAt },
      ],
      metadata: {
        radio: snapshotArg.radio,
        network: snapshotArg.network,
        coordinator: snapshotArg.coordinator,
      },
    });

    for (const device of snapshotArg.devices) {
      const deviceId = this.deviceId(device);
      devices.set(deviceId, {
        id: deviceId,
        integrationDomain: 'zha',
        name: this.deviceName(device),
        protocol: 'zigbee',
        manufacturer: device.manufacturer,
        model: device.model,
        online: device.available !== false,
        features: [
          { id: 'availability', capability: 'sensor', name: 'Availability', readable: true, writable: false },
        ],
        state: [
          { featureId: 'availability', value: device.available === false ? 'offline' : 'online', updatedAt },
        ],
        metadata: {
          ieee: device.ieee,
          nwk: device.nwk,
          lqi: device.lqi,
          rssi: device.rssi,
          powerSource: device.powerSource || device.power_source,
          quirkApplied: device.quirkApplied ?? device.quirk_applied,
          quirkClass: device.quirkClass || device.quirk_class,
        },
      });
    }

    for (const entity of this.allEntities(snapshotArg)) {
      const deviceId = this.entityDeviceId(snapshotArg, entity);
      const device = devices.get(deviceId) || devices.get(coordinatorId);
      if (!device) {
        continue;
      }
      const platform = this.corePlatform(entity.platform || 'sensor');
      const feature = {
        id: this.slug(entity.uniqueId || entity.unique_id || entity.entityId || entity.entity_id || this.entityName(entity)),
        capability: this.capabilityForPlatform(String(platform)),
        name: this.entityName(entity),
        readable: true,
        writable: this.entityWritable(entity, platform),
        unit: entity.unit || entity.unitOfMeasurement || entity.unit_of_measurement,
      } satisfies plugins.shxInterfaces.data.IDeviceFeature;
      device.features.push(feature);
      device.state.push({ featureId: feature.id, value: this.deviceStateValue(this.entityState(entity, platform)), updatedAt });
    }

    return [...devices.values()];
  }

  public static toEntities(snapshotArg: IZhaSnapshot): IIntegrationEntity[] {
    return this.allEntities(snapshotArg).map((entityArg) => {
      const platform = this.corePlatform(entityArg.platform || 'sensor');
      return {
        id: entityArg.entityId || entityArg.entity_id || `${platform}.${this.slug(this.entityName(entityArg))}`,
        uniqueId: entityArg.uniqueId || entityArg.unique_id || `zha_${this.slug(`${entityArg.ieee || entityArg.deviceIeee || entityArg.device_id || 'device'}_${entityArg.endpointId ?? entityArg.endpoint_id ?? 0}_${entityArg.clusterId ?? entityArg.cluster_id ?? 'entity'}_${this.entityName(entityArg)}`)}`,
        integrationDomain: 'zha',
        deviceId: this.entityDeviceId(snapshotArg, entityArg),
        platform,
        name: this.entityName(entityArg),
        state: this.entityState(entityArg, platform),
        attributes: {
          zhaPlatform: entityArg.platform,
          ieee: entityArg.ieee || entityArg.deviceIeee,
          endpointId: entityArg.endpointId ?? entityArg.endpoint_id,
          clusterId: entityArg.clusterId ?? entityArg.cluster_id,
          clusterType: entityArg.clusterType || entityArg.cluster_type,
          deviceClass: entityArg.deviceClass || entityArg.device_class,
          unit: entityArg.unit || entityArg.unitOfMeasurement || entityArg.unit_of_measurement,
          writable: this.entityWritable(entityArg, platform),
          raw: entityArg.raw || entityArg.infoObject || entityArg.info_object,
        },
        available: entityArg.available !== false,
      };
    });
  }

  public static commandForService(snapshotArg: IZhaSnapshot, requestArg: IServiceCallRequest): IZhaClientCommand | undefined {
    const target = this.findTargetEntity(snapshotArg, requestArg);
    if (!target) {
      return undefined;
    }
    const platform = this.corePlatform(target.platform || 'sensor');
    const payload = this.payloadForService(target, requestArg, platform);
    if (!payload) {
      return undefined;
    }
    return {
      type: `${platform}.command`,
      service: requestArg.service,
      platform: target.platform,
      ieee: target.ieee || target.deviceIeee,
      endpointId: target.endpointId ?? target.endpoint_id,
      clusterId: target.clusterId ?? target.cluster_id,
      clusterType: target.clusterType || target.cluster_type,
      entityId: target.entityId || target.entity_id,
      uniqueId: target.uniqueId || target.unique_id,
      payload,
      target: requestArg.target,
    };
  }

  public static corePlatform(platformArg: TZhaEntityPlatform): TEntityPlatform {
    const platform = String(platformArg).toLowerCase();
    if (platform === 'lock') {
      return 'switch';
    }
    if (platform === 'alarm_control_panel' || platform === 'device_tracker' || platform === 'siren' || platform === 'valve') {
      return 'sensor';
    }
    const allowed: TEntityPlatform[] = ['light', 'switch', 'sensor', 'binary_sensor', 'button', 'climate', 'cover', 'fan', 'media_player', 'number', 'select', 'text', 'update'];
    return allowed.includes(platform as TEntityPlatform) ? platform as TEntityPlatform : 'sensor';
  }

  private static allEntities(snapshotArg: IZhaSnapshot): IZhaEntityDescriptor[] {
    const entities = [...snapshotArg.entities];
    for (const device of snapshotArg.devices) {
      for (const entity of device.entities || []) {
        entities.push({ ...entity, ieee: entity.ieee || device.ieee, deviceIeee: entity.deviceIeee || device.ieee });
      }
    }
    for (const group of snapshotArg.groups) {
      for (const entity of group.entities || []) {
        entities.push({ ...entity, deviceId: entity.deviceId || entity.device_id || `zha.group.${group.groupId ?? group.group_id}`, name: entity.name || group.name });
      }
    }
    return entities;
  }

  private static findTargetEntity(snapshotArg: IZhaSnapshot, requestArg: IServiceCallRequest): IZhaEntityDescriptor | undefined {
    const entities = this.allEntities(snapshotArg);
    if (requestArg.target.entityId) {
      return entities.find((entityArg) => entityArg.entityId === requestArg.target.entityId || entityArg.entity_id === requestArg.target.entityId || entityArg.uniqueId === requestArg.target.entityId || entityArg.unique_id === requestArg.target.entityId);
    }
    if (requestArg.target.deviceId) {
      return entities.find((entityArg) => this.entityDeviceId(snapshotArg, entityArg) === requestArg.target.deviceId && this.entityWritable(entityArg, this.corePlatform(entityArg.platform || 'sensor')))
        || entities.find((entityArg) => this.entityDeviceId(snapshotArg, entityArg) === requestArg.target.deviceId);
    }
    return entities.find((entityArg) => this.entityWritable(entityArg, this.corePlatform(entityArg.platform || 'sensor')));
  }

  private static payloadForService(entityArg: IZhaEntityDescriptor, requestArg: IServiceCallRequest, platformArg: TEntityPlatform): Record<string, unknown> | undefined {
    if (requestArg.service === 'turn_on') {
      return { command: 'on', value: true };
    }
    if (requestArg.service === 'turn_off') {
      return { command: 'off', value: false };
    }
    if (requestArg.service === 'press') {
      return { command: 'press', value: true };
    }
    if (requestArg.service === 'set_value') {
      return { command: 'write_attribute', value: requestArg.data?.value };
    }
    if (requestArg.service === 'set_position' || requestArg.service === 'set_percentage') {
      return { command: 'go_to_lift_percentage', value: requestArg.data?.position ?? requestArg.data?.percentage };
    }
    if (requestArg.service === 'open_cover') {
      return { command: 'up_open', value: 100 };
    }
    if (requestArg.service === 'close_cover') {
      return { command: 'down_close', value: 0 };
    }
    if (requestArg.service === 'lock' || requestArg.service === 'unlock') {
      return { command: requestArg.service, value: requestArg.service === 'lock', code: requestArg.data?.code };
    }
    if (platformArg === 'number' && requestArg.data?.value !== undefined) {
      return { command: 'write_attribute', value: requestArg.data.value };
    }
    void entityArg;
    return undefined;
  }

  private static entityDeviceId(snapshotArg: IZhaSnapshot, entityArg: IZhaEntityDescriptor): string {
    const explicit = entityArg.deviceId || entityArg.device_id;
    if (explicit) {
      return explicit;
    }
    const ieee = entityArg.ieee || entityArg.deviceIeee;
    if (ieee) {
      return `zha.device.${this.slug(ieee)}`;
    }
    const device = snapshotArg.devices.find((deviceArg) => entityArg.deviceIeee && deviceArg.ieee === entityArg.deviceIeee);
    return device ? this.deviceId(device) : this.coordinatorDeviceId(snapshotArg);
  }

  private static deviceId(deviceArg: IZhaDevice): string {
    return `zha.device.${this.slug(deviceArg.ieee || String(deviceArg.nwk ?? this.deviceName(deviceArg)))}`;
  }

  private static coordinatorDeviceId(snapshotArg: IZhaSnapshot): string {
    return `zha.coordinator.${this.slug(snapshotArg.coordinator?.ieee || snapshotArg.radio?.serialNumber || snapshotArg.radio?.path || 'network')}`;
  }

  private static deviceName(deviceArg: IZhaDevice): string {
    return deviceArg.name || deviceArg.model || deviceArg.ieee || 'Zigbee device';
  }

  private static entityName(entityArg: IZhaEntityDescriptor): string {
    return entityArg.name || entityArg.originalName || entityArg.original_name || entityArg.fallbackName || entityArg.fallback_name || entityArg.entityId || entityArg.entity_id || 'ZHA entity';
  }

  private static entityState(entityArg: IZhaEntityDescriptor, platformArg: TEntityPlatform): unknown {
    const rawValue = entityArg.state ?? entityArg.nativeValue ?? entityArg.native_value ?? entityArg.value;
    if (platformArg === 'light' || platformArg === 'switch' || platformArg === 'fan') {
      const value = entityArg.isOn ?? entityArg.is_on ?? rawValue;
      return typeof value === 'boolean' ? value ? 'on' : 'off' : value ?? 'unknown';
    }
    if (platformArg === 'cover') {
      if (entityArg.currentCoverPosition ?? entityArg.current_cover_position) {
        return entityArg.currentCoverPosition ?? entityArg.current_cover_position;
      }
      const closed = entityArg.isClosed ?? entityArg.is_closed;
      return typeof closed === 'boolean' ? closed ? 'closed' : 'open' : rawValue ?? 'unknown';
    }
    if (platformArg === 'climate') {
      return entityArg.hvacMode || entityArg.hvac_mode || rawValue || 'unknown';
    }
    return rawValue ?? 'unknown';
  }

  private static entityWritable(entityArg: IZhaEntityDescriptor, platformArg: TEntityPlatform): boolean {
    if (entityArg.attributes?.writable === true || entityArg.attributes?.writeable === true) {
      return true;
    }
    return ['light', 'switch', 'button', 'cover', 'fan', 'number', 'select', 'climate'].includes(platformArg) || String(entityArg.platform).toLowerCase() === 'lock';
  }

  private static capabilityForPlatform(platformArg: string): plugins.shxInterfaces.data.TDeviceCapability {
    if (platformArg === 'light') {
      return 'light';
    }
    if (platformArg === 'cover') {
      return 'cover';
    }
    if (platformArg === 'climate') {
      return 'climate';
    }
    if (platformArg === 'fan') {
      return 'fan';
    }
    return platformArg === 'switch' || platformArg === 'button' || platformArg === 'number' || platformArg === 'select' ? 'switch' : 'sensor';
  }

  private static deviceStateValue(valueArg: unknown): plugins.shxInterfaces.data.TDeviceStateValue {
    if (typeof valueArg === 'string' || typeof valueArg === 'number' || typeof valueArg === 'boolean' || valueArg === null || this.isRecord(valueArg)) {
      return valueArg;
    }
    return valueArg === undefined ? null : String(valueArg);
  }

  private static isRecord(valueArg: unknown): valueArg is Record<string, unknown> {
    return typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
  }

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