import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
import type { IMedcomBleConfig } from './medcom_ble.types.js';
import { medcomBleProfile } from './medcom_ble.types.js';

export class MedcomBleMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IMedcomBleConfig>, 'profile'>): ISimpleLocalSnapshot {
    if (isMedcomBleRawData(optionsArg.rawData)) {
      return toMedcomBleSnapshot(optionsArg.config, optionsArg.rawData, optionsArg.online, optionsArg.source, optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: medcomBleProfile });
  }

  public static toSnapshotFromRaw(configArg: IMedcomBleConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({ profile: medcomBleProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
  }

  public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
    return SimpleLocalMapper.toDevices(medcomBleProfile, snapshotArg);
  }

  public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
    return SimpleLocalMapper.toEntities(medcomBleProfile, snapshotArg);
  }

  public static slug(valueArg: unknown): string {
    return SimpleLocalMapper.slug(valueArg);
  }
}

const toMedcomBleSnapshot = (configArg: IMedcomBleConfig, rawDataArg: Record<string, unknown>, onlineArg = true, sourceArg: ISimpleLocalSnapshot['source'] = 'client', errorArg?: string): ISimpleLocalSnapshot => {
  const sensors = recordValue(rawDataArg.sensors) || rawDataArg;
  const cpm = numberValue(sensors.cpm);
  const address = stringValue(rawDataArg.address) || configArg.macAddress || configArg.host;
  return {
    device: {
      id: configArg.uniqueId || configArg.deviceId || stringValue(rawDataArg.identifier) || address || medcomBleProfile.domain,
      name: configArg.name || stringValue(rawDataArg.name) || stringValue(rawDataArg.model) || medcomBleProfile.defaultName || medcomBleProfile.displayName,
      manufacturer: stringValue(rawDataArg.manufacturer) || medcomBleProfile.manufacturer,
      model: stringValue(rawDataArg.model) || medcomBleProfile.model,
      serialNumber: stringValue(rawDataArg.identifier),
      host: address,
      protocol: 'local',
      attributes: cleanAttributes({ hwVersion: rawDataArg.hw_version ?? rawDataArg.hwVersion, swVersion: rawDataArg.sw_version ?? rawDataArg.swVersion, source: sourceArg, ...configArg.metadata }),
    },
    entities: cpm === undefined ? [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: onlineArg, available: true }] : [{ id: 'cpm', name: 'Counts per minute', platform: 'sensor', state: cpm, available: onlineArg, unit: 'CPM', stateClass: 'measurement', attributes: { key: 'cpm' } }],
    online: onlineArg,
    updatedAt: stringValue(rawDataArg.fetchedAt) || new Date().toISOString(),
    source: sourceArg,
    rawData: rawDataArg,
    error: errorArg,
    capabilities: { localRead: true, localControl: false, liveControlRequiresExecutor: true },
  };
};

const isMedcomBleRawData = (valueArg: unknown): valueArg is Record<string, unknown> => Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) && ('sensors' in valueArg || 'cpm' in valueArg || 'identifier' in valueArg));
const recordValue = (valueArg: unknown): Record<string, unknown> | undefined => valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : undefined;
const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
const numberValue = (valueArg: unknown): number | undefined => typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : typeof valueArg === 'string' && Number.isFinite(Number(valueArg)) ? Number(valueArg) : undefined;
const cleanAttributes = (valueArg: Record<string, unknown>): Record<string, unknown> | undefined => {
  const clean = Object.fromEntries(Object.entries(valueArg).filter(([, entryArg]) => entryArg !== undefined));
  return Object.keys(clean).length ? clean : undefined;
};
