import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
import type { IEmonitorConfig } from './emonitor.types.js';
import { emonitorDefaultName, emonitorProfile } from './emonitor.types.js';

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

  public static toSnapshotFromRaw(configArg: IEmonitorConfig, 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(emonitorProfile, snapshotArg);
  }

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

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

  private static normalizeRawData(configArg: IEmonitorConfig, rawDataArg: unknown): unknown {
    if (!isRecord(rawDataArg) || isSnapshotLike(rawDataArg) || hasSimpleEntities(rawDataArg)) {
      return rawDataArg;
    }

    const channels = channelRecords(rawDataArg.channels);
    if (!channels.length) {
      return rawDataArg;
    }

    const network = recordValue(rawDataArg.network) || {};
    const hardware = recordValue(rawDataArg.hardware) || {};
    const macAddress = configArg.macAddress || configArg.mac_address || stringValue(network.mac_address) || stringValue(network.macAddress) || stringValue(rawDataArg.mac_address) || stringValue(rawDataArg.macAddress);
    const serialNumber = stringValue(hardware.serial_number) || stringValue(hardware.serialNumber) || stringValue(rawDataArg.serial_number) || stringValue(rawDataArg.serialNumber);
    const firmwareVersion = stringValue(hardware.firmware_version) || stringValue(hardware.firmwareVersion) || stringValue(rawDataArg.firmware_version) || stringValue(rawDataArg.firmwareVersion);
    const deviceName = configArg.name || stringValue(rawDataArg.name) || (macAddress ? `Emonitor ${shortMac(macAddress)}` : emonitorDefaultName);
    const entities: ISimpleLocalEntitySnapshot[] = [];
    const seenChannels = new Set<string>();

    for (const channel of channels) {
      seenChannels.add(channel.number);
      if (channel.data.active === false) {
        continue;
      }
      const pairedChannel = stringValue(channel.data.paired_with_channel) || stringValue(channel.data.pairedWithChannel) || numberString(channel.data.paired_with_channel) || numberString(channel.data.pairedWithChannel);
      if (pairedChannel && seenChannels.has(pairedChannel)) {
        continue;
      }

      const label = stringValue(channel.data.label) || channel.number;
      entities.push(...powerEntities({ channelNumber: channel.number, label, data: channel.data, pairedData: pairedChannel ? channels.find((candidateArg) => candidateArg.number === pairedChannel)?.data : undefined, macAddress, deviceName }));
    }

    return {
      device: {
        id: configArg.uniqueId || macAddress || serialNumber || configArg.host || deviceName,
        name: deviceName,
        manufacturer: emonitorProfile.manufacturer,
        model: emonitorProfile.model,
        serialNumber,
        host: configArg.host,
        port: configArg.port,
        protocol: emonitorProfile.defaultProtocol,
        attributes: {
          firmwareVersion,
          macAddress,
        },
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const powerEntities = (optionsArg: { channelNumber: string; label: string; data: Record<string, unknown>; pairedData?: Record<string, unknown>; macAddress?: string; deviceName: string }): ISimpleLocalEntitySnapshot[] => {
  const base = SimpleLocalMapper.slug(optionsArg.macAddress || optionsArg.deviceName);
  return [
    ['inst_power', optionsArg.label],
    ['avg_power', `${optionsArg.label} average`],
    ['max_power', `${optionsArg.label} max`],
  ].map(([keyArg, nameArg]) => ({
    id: `channel_${SimpleLocalMapper.slug(optionsArg.channelNumber)}_${keyArg}`,
    uniqueId: `${emonitorProfile.domain}_${base}_${SimpleLocalMapper.slug(optionsArg.channelNumber)}_${keyArg}`,
    name: nameArg,
    platform: 'sensor',
    state: sumPower(optionsArg.data, optionsArg.pairedData, keyArg),
    available: true,
    writable: false,
    unit: 'W',
    deviceClass: 'power',
    stateClass: 'measurement',
    attributes: {
      channel: Number(optionsArg.channelNumber),
      pairedWithChannel: numberValue(optionsArg.data.paired_with_channel) ?? numberValue(optionsArg.data.pairedWithChannel),
    },
  }));
};

const sumPower = (channelArg: Record<string, unknown>, pairedChannelArg: Record<string, unknown> | undefined, keyArg: string): number => (numberValue(channelArg[keyArg]) || 0) + (pairedChannelArg ? numberValue(pairedChannelArg[keyArg]) || 0 : 0);

const channelRecords = (valueArg: unknown): Array<{ number: string; data: Record<string, unknown> }> => {
  if (Array.isArray(valueArg)) {
    return valueArg.filter(isRecord).map((channelArg, indexArg) => ({ number: stringValue(channelArg.channel) || stringValue(channelArg.channel_number) || String(indexArg + 1), data: channelArg }));
  }
  if (isRecord(valueArg)) {
    return Object.entries(valueArg).filter((entryArg): entryArg is [string, Record<string, unknown>] => isRecord(entryArg[1])).map(([numberArg, dataArg]) => ({ number: numberArg, data: dataArg }));
  }
  return [];
};

const shortMac = (valueArg: string): string => valueArg.replace(/[^a-fA-F0-9]/g, '').slice(-6).toUpperCase();
const hasSimpleEntities = (valueArg: Record<string, unknown>): boolean => Array.isArray(valueArg.entities) && valueArg.entities.some((entityArg) => isRecord(entityArg) && 'name' in entityArg && 'state' in entityArg);
const isSnapshotLike = (valueArg: Record<string, unknown>): boolean => isRecord(valueArg.device) && Array.isArray(valueArg.entities);
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 numberString = (valueArg: unknown): string | undefined => typeof valueArg === 'number' && Number.isFinite(valueArg) ? String(valueArg) : 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;
};
