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 { INamConfig, INamRawApiData, TNamRawData } from './nam.types.js';
import { namDefaultName, namDefaultPort, namProfile } from './nam.types.js';

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

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

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

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

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

  public static offlineSnapshot(configArg: INamConfig, errorArg: string): ISimpleLocalSnapshot {
    return this.toSnapshot({ config: configArg, online: false, source: 'runtime', error: errorArg });
  }

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

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

    const endpoint = endpointFromConfig(configArg);
    const macAddress = stringValue(rawObject.macAddress) || configArg.macAddress;
    const normalizedMac = macAddress?.replace(/:/g, '').toLowerCase();
    const entities = entitiesFromSensors(sensors);

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || normalizedMac || (endpoint ? `${endpoint.host}:${endpoint.port}` : undefined) || namDefaultName,
        name: configArg.name || namDefaultName,
        manufacturer: 'Nettigo',
        model: 'Nettigo Air Monitor',
        serialNumber: normalizedMac,
        host: endpoint?.host,
        port: endpoint?.port,
        protocol: 'http',
        configurationUrl: endpoint ? `http://${endpoint.host}${endpoint.port === 80 ? '' : `:${endpoint.port}`}/` : undefined,
        attributes: cleanAttributes({
          softwareVersion: stringValue(rawObject.softwareVersion),
          macAddress,
        }),
      },
      entities,
      macAddress,
      softwareVersion: stringValue(rawObject.softwareVersion),
      uptime: numberValue(rawObject.uptime),
      sensors,
      rawDeviceData: objectValue(rawObject.rawDeviceData),
      fetchedAt: stringValue(rawObject.fetchedAt) || new Date().toISOString(),
    } satisfies INamRawApiData & { device: Record<string, unknown>; entities: ISimpleLocalEntitySnapshot[] };
  }
}

const sensorMetadata: Record<string, { name: string; unit?: string; deviceClass?: string; stateClass?: string; entityCategory?: string }> = {
  bh1750_illuminance: { name: 'BH1750 Illuminance', unit: 'lx', deviceClass: 'illuminance', stateClass: 'measurement' },
  bme280_humidity: { name: 'BME280 Humidity', unit: '%', deviceClass: 'humidity', stateClass: 'measurement' },
  bme280_pressure: { name: 'BME280 Pressure', unit: 'hPa', deviceClass: 'pressure', stateClass: 'measurement' },
  bme280_temperature: { name: 'BME280 Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  bmp180_pressure: { name: 'BMP180 Pressure', unit: 'hPa', deviceClass: 'pressure', stateClass: 'measurement' },
  bmp180_temperature: { name: 'BMP180 Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  bmp280_pressure: { name: 'BMP280 Pressure', unit: 'hPa', deviceClass: 'pressure', stateClass: 'measurement' },
  bmp280_temperature: { name: 'BMP280 Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  dht22_humidity: { name: 'DHT22 Humidity', unit: '%', deviceClass: 'humidity', stateClass: 'measurement' },
  dht22_temperature: { name: 'DHT22 Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  ds18b20_temperature: { name: 'DS18B20 Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  heca_humidity: { name: 'HECA Humidity', unit: '%', deviceClass: 'humidity', stateClass: 'measurement' },
  heca_temperature: { name: 'HECA Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  mhz14a_carbon_dioxide: { name: 'MH-Z14A Carbon Dioxide', unit: 'ppm', deviceClass: 'carbon_dioxide', stateClass: 'measurement' },
  pms_p0: { name: 'PMSx003 PM1', unit: 'ug/m3', deviceClass: 'pm1', stateClass: 'measurement' },
  pms_p1: { name: 'PMSx003 PM10', unit: 'ug/m3', deviceClass: 'pm10', stateClass: 'measurement' },
  pms_p2: { name: 'PMSx003 PM2.5', unit: 'ug/m3', deviceClass: 'pm25', stateClass: 'measurement' },
  sds011_p1: { name: 'SDS011 PM10', unit: 'ug/m3', deviceClass: 'pm10', stateClass: 'measurement' },
  sds011_p2: { name: 'SDS011 PM2.5', unit: 'ug/m3', deviceClass: 'pm25', stateClass: 'measurement' },
  sht3x_humidity: { name: 'SHT3x Humidity', unit: '%', deviceClass: 'humidity', stateClass: 'measurement' },
  sht3x_temperature: { name: 'SHT3x Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  signal: { name: 'Signal', unit: 'dBm', deviceClass: 'signal_strength', stateClass: 'measurement', entityCategory: 'diagnostic' },
  sps30_p0: { name: 'SPS30 PM1', unit: 'ug/m3', deviceClass: 'pm1', stateClass: 'measurement' },
  sps30_p1: { name: 'SPS30 PM10', unit: 'ug/m3', deviceClass: 'pm10', stateClass: 'measurement' },
  sps30_p2: { name: 'SPS30 PM2.5', unit: 'ug/m3', deviceClass: 'pm25', stateClass: 'measurement' },
  sps30_p4: { name: 'SPS30 PM4', unit: 'ug/m3', deviceClass: 'pm4', stateClass: 'measurement' },
  uptime: { name: 'Uptime', unit: 's', deviceClass: 'duration', entityCategory: 'diagnostic' },
};

const entitiesFromSensors = (sensorsArg: Record<string, number>): ISimpleLocalEntitySnapshot[] => {
  return Object.entries(sensorsArg).map(([keyArg, valueArg]) => {
    const metadata = sensorMetadata[keyArg] || { name: titleCase(keyArg) };
    return {
      id: keyArg,
      name: metadata.name,
      platform: 'sensor',
      state: valueArg,
      unit: metadata.unit,
      deviceClass: metadata.deviceClass,
      stateClass: metadata.stateClass,
      attributes: cleanAttributes({
        sourceKey: keyArg,
        entityCategory: metadata.entityCategory,
      }),
    };
  });
};

const endpointFromConfig = (configArg: INamConfig): { host: string; port: number } | undefined => {
  const rawUrl = stringValue(configArg.url) || stringValue(configArg.host);
  if (!rawUrl) {
    return undefined;
  }
  try {
    const url = new URL(rawUrl.includes('://') ? rawUrl : `http://${rawUrl}`);
    return { host: url.hostname, port: configArg.port || numberValue(url.port) || namDefaultPort };
  } catch {
    return { host: rawUrl, port: configArg.port || namDefaultPort };
  }
};

const sensorsValue = (valueArg: unknown): Record<string, number> | undefined => {
  const value = objectValue(valueArg);
  if (!value) {
    return undefined;
  }
  const entries = Object.entries(value)
    .map(([keyArg, itemArg]) => [keyArg, numberValue(itemArg)] as const)
    .filter((entryArg): entryArg is readonly [string, number] => entryArg[1] !== undefined);
  return Object.fromEntries(entries);
};

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 unknown;
  } 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 titleCase = (valueArg: string): string => {
  return valueArg.replace(/_/g, ' ').replace(/\b\w/g, (charArg) => charArg.toUpperCase());
};

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