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 { IIotawattConfig, IIotawattRawApiData, IIotawattSensorSnapshot, TIotawattRawData } from './iotawatt.types.js';
import { iotawattDefaultName, iotawattDefaultPort, iotawattProfile } from './iotawatt.types.js';

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

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

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

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

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

  public static normalizeRawData(rawDataArg: unknown, configArg: IIotawattConfig): unknown {
    const parsedRawData = parseMaybeJson(rawDataArg);
    const rawObject = objectValue(parsedRawData);
    if (!rawObject || Array.isArray(rawObject.entities) || rawObject.device) {
      return parsedRawData;
    }

    const sensors = sensorsValue(rawObject.sensors);
    const status = objectValue(rawObject.status);
    if (!sensors && !status) {
      return parsedRawData;
    }

    const wifi = objectValue(status?.wifi);
    const macAddress = stringValue(configArg.macAddress) || stringValue(wifi?.mac);
    const normalizedMac = macAddress?.replace(/:/g, '').toUpperCase();
    const host = configArg.host || hostFromUrl(configArg.url);
    const port = configArg.port || (host ? iotawattDefaultPort : undefined);
    const entities = entitiesFromSensors(sensors || {});

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || normalizedMac || (host ? `${host}:${port || iotawattDefaultPort}` : undefined) || iotawattDefaultName,
        name: configArg.name || iotawattDefaultName,
        manufacturer: 'IoTaWatt',
        model: 'IoTaWatt',
        serialNumber: normalizedMac,
        host,
        port,
        protocol: 'http',
      },
      entities,
      sensors,
      status,
      series: rawObject.series,
      fetchedAt: stringValue(rawObject.fetchedAt) || new Date().toISOString(),
    };
  }
}

const unitMetadata: Record<string, { unit?: string; deviceClass?: string; stateClass?: string; entityCategory?: string; value?: (valueArg: unknown) => unknown }> = {
  Amps: { unit: 'A', deviceClass: 'current', stateClass: 'measurement', entityCategory: 'diagnostic' },
  Hz: { unit: 'Hz', deviceClass: 'frequency', stateClass: 'measurement', entityCategory: 'diagnostic' },
  PF: { unit: '%', deviceClass: 'power_factor', stateClass: 'measurement', entityCategory: 'diagnostic', value: (valueArg) => numberValue(valueArg) === undefined ? valueArg : numberValue(valueArg)! * 100 },
  Watts: { unit: 'W', deviceClass: 'power', stateClass: 'measurement' },
  WattHours: { unit: 'Wh', deviceClass: 'energy', stateClass: 'total' },
  VA: { unit: 'VA', deviceClass: 'apparent_power', stateClass: 'measurement', entityCategory: 'diagnostic' },
  VAR: { unit: 'VAR', stateClass: 'measurement', entityCategory: 'diagnostic' },
  VARh: { unit: 'VARh', stateClass: 'measurement', entityCategory: 'diagnostic' },
  Volts: { unit: 'V', deviceClass: 'voltage', stateClass: 'measurement', entityCategory: 'diagnostic' },
};

const entitiesFromSensors = (sensorsArg: Record<string, IIotawattSensorSnapshot>): ISimpleLocalEntitySnapshot[] => {
  return Object.entries(sensorsArg).map(([keyArg, sensorArg]) => {
    const metadata = unitMetadata[sensorArg.unit] || {};
    const state = metadata.value ? metadata.value(sensorArg.value) : sensorArg.value;
    return {
      id: keyArg,
      name: sensorArg.name || sensorArg.sourceName || keyArg,
      platform: 'sensor',
      state: state ?? null,
      attributes: cleanAttributes({
        key: keyArg,
        type: sensorArg.type,
        channel: sensorArg.type === 'Input' ? sensorArg.channel : undefined,
        sourceName: sensorArg.sourceName,
        baseName: sensorArg.baseName,
        begin: sensorArg.begin,
        fromStart: sensorArg.fromStart,
        nativeUnitOfMeasurement: metadata.unit || sensorArg.unit,
        deviceClass: metadata.deviceClass,
        stateClass: metadata.stateClass,
        entityCategory: metadata.entityCategory,
      }),
    };
  });
};

const sensorsValue = (valueArg: unknown): Record<string, IIotawattSensorSnapshot> | undefined => {
  if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
    return undefined;
  }
  return valueArg as Record<string, IIotawattSensorSnapshot>;
};

const objectValue = (valueArg: unknown): Record<string, unknown> | undefined => {
  return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : undefined;
};

const parseMaybeJson = (valueArg: unknown): unknown => {
  if (typeof valueArg !== 'string') {
    return valueArg;
  }
  try {
    return JSON.parse(valueArg) as IIotawattRawApiData;
  } catch {
    return valueArg;
  }
};

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

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

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

const hostFromUrl = (urlArg: string | undefined): string | undefined => {
  if (!urlArg) {
    return undefined;
  }
  try {
    return new URL(urlArg.includes('://') ? urlArg : `http://${urlArg}`).hostname;
  } catch {
    return undefined;
  }
};
