import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IIntegrationEvent } from '../../core/types.js';
import type { IDsmrEvent, IDsmrSensorState, IDsmrSnapshot, TDsmrDeviceType } from './dsmr.types.js';
import { dsmrDomain } from './dsmr.types.js';

export class DsmrMapper {
  public static toDevices(snapshotArg: IDsmrSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [this.deviceForType(snapshotArg, 'electricity', updatedAt)];
    if (snapshotArg.meter.serialIdGas || snapshotArg.sensors.some((sensorArg) => sensorArg.deviceType === 'gas')) {
      devices.push(this.deviceForType(snapshotArg, 'gas', updatedAt));
    }
    if (snapshotArg.sensors.some((sensorArg) => sensorArg.deviceType === 'water')) {
      devices.push(this.deviceForType(snapshotArg, 'water', updatedAt));
    }
    if (snapshotArg.sensors.some((sensorArg) => sensorArg.deviceType === 'heat')) {
      devices.push(this.deviceForType(snapshotArg, 'heat', updatedAt));
    }
    return devices;
  }

  public static toEntities(snapshotArg: IDsmrSnapshot): IIntegrationEntity[] {
    const usedIds = new Map<string, number>();
    return snapshotArg.sensors.map((sensorArg) => this.toEntity(snapshotArg, sensorArg, usedIds));
  }

  public static toIntegrationEvent(eventArg: IDsmrEvent): IIntegrationEvent {
    return {
      type: eventArg.type === 'refresh_failed' ? 'error' : 'state_changed',
      integrationDomain: dsmrDomain,
      deviceId: eventArg.snapshot && eventArg.sensor ? this.deviceId(eventArg.snapshot, eventArg.sensor.deviceType) : undefined,
      entityId: eventArg.snapshot && eventArg.sensor ? this.entityId(eventArg.sensor, new Map()) : undefined,
      data: eventArg,
      timestamp: eventArg.timestamp,
    };
  }

  public static deviceId(snapshotArg: IDsmrSnapshot, deviceTypeArg: TDsmrDeviceType): string {
    const serial = deviceTypeArg === 'gas'
      ? snapshotArg.meter.serialIdGas || `${snapshotArg.meter.id}_gas`
      : snapshotArg.meter.serialId || `${snapshotArg.meter.id}_${deviceTypeArg}`;
    return `${dsmrDomain}.${deviceTypeArg}.${this.slug(String(serial || snapshotArg.meter.id))}`;
  }

  public static entityId(sensorArg: IDsmrSensorState, usedIdsArg: Map<string, number>): string {
    const baseId = `sensor.${this.slug(`${this.deviceName(sensorArg.deviceType)} ${sensorArg.name}`)}`;
    const seen = usedIdsArg.get(baseId) || 0;
    usedIdsArg.set(baseId, seen + 1);
    return seen ? `${baseId}_${seen + 1}` : baseId;
  }

  private static deviceForType(snapshotArg: IDsmrSnapshot, deviceTypeArg: TDsmrDeviceType, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    const sensors = snapshotArg.sensors.filter((sensorArg) => sensorArg.deviceType === deviceTypeArg);
    return {
      id: this.deviceId(snapshotArg, deviceTypeArg),
      integrationDomain: dsmrDomain,
      name: this.deviceName(deviceTypeArg),
      protocol: 'unknown',
      manufacturer: snapshotArg.meter.manufacturer || 'DSMR',
      model: deviceTypeArg === 'electricity' ? snapshotArg.meter.model || `DSMR ${snapshotArg.meter.dsmrVersion}` : `${this.deviceName(deviceTypeArg)} DSMR meter`,
      online: snapshotArg.connected,
      features: [
        { id: 'connection', capability: 'sensor', name: 'Connection', readable: true, writable: false },
        ...sensors.map((sensorArg) => ({
          id: sensorArg.key,
          capability: 'sensor' as const,
          name: sensorArg.name,
          readable: true,
          writable: false,
          unit: sensorArg.unit,
        })),
      ],
      state: [
        { featureId: 'connection', value: snapshotArg.connected ? 'online' : 'offline', updatedAt: updatedAtArg },
        ...sensors.map((sensorArg) => ({ featureId: sensorArg.key, value: sensorArg.value, updatedAt: sensorArg.updatedAt || updatedAtArg })),
      ],
      metadata: this.cleanAttributes({
        source: snapshotArg.source,
        dsmrVersion: snapshotArg.meter.dsmrVersion,
        connectionType: snapshotArg.meter.connectionType,
        host: snapshotArg.meter.host,
        port: snapshotArg.meter.port,
        serialPort: snapshotArg.meter.serialPort,
        protocol: snapshotArg.meter.protocol,
        serialId: deviceTypeArg === 'gas' ? snapshotArg.meter.serialIdGas : snapshotArg.meter.serialId,
      }),
    };
  }

  private static toEntity(snapshotArg: IDsmrSnapshot, sensorArg: IDsmrSensorState, usedIdsArg: Map<string, number>): IIntegrationEntity {
    return {
      id: this.entityId(sensorArg, usedIdsArg),
      uniqueId: this.uniqueId(snapshotArg, sensorArg),
      integrationDomain: dsmrDomain,
      deviceId: this.deviceId(snapshotArg, sensorArg.deviceType),
      platform: 'sensor',
      name: sensorArg.name,
      state: sensorArg.value,
      attributes: this.cleanAttributes({
        key: sensorArg.key,
        obisReference: sensorArg.obisReference,
        obisCode: sensorArg.obisCode,
        unitOfMeasurement: sensorArg.unit,
        deviceClass: sensorArg.deviceClass,
        stateClass: sensorArg.stateClass,
        entityCategory: sensorArg.entityCategory,
        enabledByDefault: sensorArg.enabledByDefault,
        channel: sensorArg.channel,
        rawValue: sensorArg.rawValue,
        dsmrVersion: snapshotArg.meter.dsmrVersion,
        source: snapshotArg.source,
        ...sensorArg.attributes,
      }),
      available: snapshotArg.connected && sensorArg.available,
    };
  }

  private static uniqueId(snapshotArg: IDsmrSnapshot, sensorArg: IDsmrSensorState): string {
    const serial = sensorArg.serialId || (sensorArg.deviceType === 'gas' ? snapshotArg.meter.serialIdGas : snapshotArg.meter.serialId) || snapshotArg.meter.id;
    return `${this.slug(String(serial))}_${sensorArg.key}`;
  }

  private static deviceName(deviceTypeArg: TDsmrDeviceType): string {
    if (deviceTypeArg === 'gas') {
      return 'Gas Meter';
    }
    if (deviceTypeArg === 'water') {
      return 'Water Meter';
    }
    if (deviceTypeArg === 'heat') {
      return 'Heat Meter';
    }
    return 'Electricity Meter';
  }

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

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