import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TEntityPlatform, type TSimpleLocalRawData } from '../../core/index.js';
import type { IEnoceanConfig } from './enocean.types.js';
import { enoceanDefaultName, enoceanProfile } from './enocean.types.js';

export class EnoceanMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IEnoceanConfig>, 'profile'>): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({
      ...optionsArg,
      profile: enoceanProfile,
      rawData: this.normalizeRawData(optionsArg.config, optionsArg.rawData),
    });
  }

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

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

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

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

  private static normalizeRawData(configArg: IEnoceanConfig, rawDataArg: unknown): unknown {
    if (!isRecord(rawDataArg) || ('device' in rawDataArg && 'entities' in rawDataArg)) {
      return rawDataArg;
    }

    const gateway = recordValue(rawDataArg.gateway) || rawDataArg;
    const records = recordsFrom(rawDataArg.telegrams ?? rawDataArg.devices ?? rawDataArg.sensors ?? rawDataArg.values);
    if (!records.length) {
      return rawDataArg;
    }

    const devicePath = configArg.device || stringValue(rawDataArg.device) || stringValue(gateway.device) || configArg.host;
    const name = configArg.name || stringValue(rawDataArg.name) || stringValue(gateway.name) || enoceanDefaultName;
    const entities = records.map((recordArg) => this.entityFromRecord(recordArg, configArg, devicePath || name)).filter((entityArg): entityArg is ISimpleLocalEntitySnapshot => Boolean(entityArg));

    return {
      device: {
        id: configArg.uniqueId || devicePath || name,
        name,
        manufacturer: stringValue(gateway.manufacturer) || enoceanProfile.manufacturer,
        model: stringValue(gateway.model) || enoceanProfile.model,
        serialNumber: stringValue(gateway.serialNumber) || stringValue(gateway.serial_number),
        host: configArg.host,
        protocol: enoceanProfile.defaultProtocol,
        attributes: {
          device: devicePath,
          baseAddress: addressValue(gateway.baseAddress ?? gateway.base_address),
          signalReceiveMessage: 'enocean.receive_message',
          signalSendMessage: 'enocean.send_message',
        },
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }

  private static entityFromRecord(recordArg: Record<string, unknown>, configArg: IEnoceanConfig, uniqueBaseArg: string): ISimpleLocalEntitySnapshot | undefined {
    const address = addressValue(recordArg.id ?? recordArg.dev_id ?? recordArg.deviceId ?? recordArg.address ?? recordArg.sender);
    const id = stringValue(recordArg.entityId) || stringValue(recordArg.entity_id) || address || stringValue(recordArg.name);
    if (!id) {
      return undefined;
    }

    const deviceClass = stringValue(recordArg.deviceClass) || stringValue(recordArg.device_class) || stringValue(recordArg.type) || configArg.deviceClass || configArg.device_class;
    const platform = platformValue(recordArg.platform) || platformFromDeviceClass(deviceClass, recordArg.state);
    const state = stateValue(recordArg, deviceClass);

    return {
      id: `${platform}_${SimpleLocalMapper.slug(id)}`,
      uniqueId: `${enoceanProfile.domain}_${SimpleLocalMapper.slug(uniqueBaseArg)}_${SimpleLocalMapper.slug(id)}`,
      name: stringValue(recordArg.name) || titleCase(id),
      platform,
      state,
      available: recordArg.available === undefined ? true : booleanValue(recordArg.available),
      writable: platform === 'light' || platform === 'switch',
      unit: unitForDeviceClass(deviceClass),
      deviceClass: normalizedDeviceClass(deviceClass),
      attributes: {
        address,
        channel: numberValue(recordArg.channel ?? configArg.channel),
        eep: stringValue(recordArg.eep),
        rorg: recordArg.rorg,
        rawAction: recordArg.action,
        which: recordArg.which,
        onoff: recordArg.onoff,
      },
    };
  }
}

const stateValue = (recordArg: Record<string, unknown>, deviceClassArg: string | undefined): unknown => {
  const explicit = recordArg.state ?? recordArg.value ?? recordArg.native_value ?? recordArg.nativeValue;
  const normalized = normalizedDeviceClass(deviceClassArg);
  if (explicit !== undefined) {
    if (['light', 'motion', 'opening', 'switch'].includes(normalized || '')) {
      return booleanValue(explicit) ?? explicit;
    }
    return explicit;
  }

  const telegramData = Array.isArray(recordArg.telegram_data) ? recordArg.telegram_data : Array.isArray(recordArg.telegramData) ? recordArg.telegramData : undefined;
  if (telegramData && normalized === 'temperature') {
    return numberValue(telegramData[2]);
  }
  if (telegramData && normalized === 'humidity') {
    const value = numberValue(telegramData[1]);
    return value === undefined ? undefined : Math.round((value * 100 / 250) * 10) / 10;
  }
  if (telegramData && normalized === 'power') {
    return numberValue(telegramData[0]);
  }
  return booleanValue(recordArg.is_on ?? recordArg.isOn ?? recordArg.on) ?? null;
};

const recordsFrom = (valueArg: unknown): Array<Record<string, unknown>> => {
  if (Array.isArray(valueArg)) {
    return valueArg.filter(isRecord);
  }
  if (isRecord(valueArg)) {
    return Object.entries(valueArg).flatMap(([keyArg, entryArg]) => isRecord(entryArg) ? [{ id: keyArg, ...entryArg }] : []);
  }
  return [];
};

const platformValue = (valueArg: unknown): TEntityPlatform | undefined => {
  const value = stringValue(valueArg);
  return value && ['binary_sensor', 'light', 'sensor', 'switch'].includes(value) ? value as TEntityPlatform : undefined;
};

const platformFromDeviceClass = (deviceClassArg: string | undefined, stateArg: unknown): TEntityPlatform => {
  const value = normalizedDeviceClass(deviceClassArg);
  if (value === 'switch') {
    return 'switch';
  }
  if (value === 'light') {
    return 'light';
  }
  if (value === 'opening' || value === 'motion') {
    return 'binary_sensor';
  }
  return typeof stateArg === 'boolean' ? 'binary_sensor' : 'sensor';
};

const normalizedDeviceClass = (valueArg: unknown): string | undefined => {
  const value = stringValue(valueArg)?.toLowerCase();
  if (!value) {
    return undefined;
  }
  const map: Record<string, string> = {
    humidity: 'humidity',
    powersensor: 'power',
    power: 'power',
    temperature: 'temperature',
    windowhandle: 'opening',
    window_handle: 'opening',
    switch: 'switch',
    light: 'light',
    contact: 'opening',
    motion: 'motion',
  };
  return map[value] || value;
};

const unitForDeviceClass = (valueArg: unknown): string | undefined => {
  const value = normalizedDeviceClass(valueArg);
  if (value === 'temperature') {
    return 'C';
  }
  if (value === 'humidity') {
    return '%';
  }
  if (value === 'power') {
    return 'W';
  }
  return undefined;
};

const addressValue = (valueArg: unknown): string | undefined => {
  if (Array.isArray(valueArg)) {
    return valueArg.map((partArg) => Number(partArg).toString(16).padStart(2, '0')).join('').toUpperCase();
  }
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return Math.round(valueArg).toString(16).toUpperCase();
  }
  return stringValue(valueArg);
};

const recordValue = (valueArg: unknown): Record<string, unknown> | undefined => isRecord(valueArg) ? valueArg : undefined;

const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg));

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const numberValue = (valueArg: unknown): number | undefined => {
  const value = typeof valueArg === 'number' ? valueArg : typeof valueArg === 'string' ? Number(valueArg) : undefined;
  return value !== undefined && Number.isFinite(value) ? value : undefined;
};

const booleanValue = (valueArg: unknown): boolean | undefined => {
  if (typeof valueArg === 'boolean') {
    return valueArg;
  }
  if (typeof valueArg === 'number') {
    return valueArg !== 0;
  }
  const value = stringValue(valueArg)?.toLowerCase();
  if (!value) {
    return undefined;
  }
  if (['1', 'true', 'yes', 'on', 'open', 'opened', 'active'].includes(value)) {
    return true;
  }
  if (['0', 'false', 'no', 'off', 'closed', 'close', 'inactive'].includes(value)) {
    return false;
  }
  return undefined;
};

const titleCase = (valueArg: string): string => valueArg.replace(/[_-]+/g, ' ').replace(/\b\w/g, (charArg) => charArg.toUpperCase());
