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 { IFibaroConfig } from './fibaro.types.js';
import { fibaroDefaultName, fibaroProfile } from './fibaro.types.js';

const fibaroTypePlatformMap: Record<string, TEntityPlatform> = {
  'com.fibaro.multilevelSensor': 'sensor',
  'com.fibaro.binarySwitch': 'switch',
  'com.fibaro.multilevelSwitch': 'switch',
  'com.fibaro.FGD212': 'light',
  'com.fibaro.FGR': 'cover',
  'com.fibaro.doorSensor': 'binary_sensor',
  'com.fibaro.doorWindowSensor': 'binary_sensor',
  'com.fibaro.FGMS001': 'binary_sensor',
  'com.fibaro.heatDetector': 'binary_sensor',
  'com.fibaro.lifeDangerSensor': 'binary_sensor',
  'com.fibaro.smokeSensor': 'binary_sensor',
  'com.fibaro.remoteSwitch': 'switch',
  'com.fibaro.sensor': 'sensor',
  'com.fibaro.colorController': 'light',
  'com.fibaro.securitySensor': 'binary_sensor',
  'com.fibaro.hvac': 'climate',
  'com.fibaro.hvacSystem': 'climate',
  'com.fibaro.setpoint': 'climate',
  'com.fibaro.FGT001': 'climate',
  'com.fibaro.thermostatDanfoss': 'climate',
  'com.fibaro.doorLock': 'binary_sensor',
  'com.fibaro.binarySensor': 'binary_sensor',
  'com.fibaro.accelerometer': 'binary_sensor',
};

const sensorDeviceClassMap: Record<string, string> = {
  'com.fibaro.temperatureSensor': 'temperature',
  'com.fibaro.humiditySensor': 'humidity',
  'com.fibaro.lightSensor': 'illuminance',
  'com.fibaro.energyMeter': 'energy',
  CO2: 'co2',
};

const binarySensorDeviceClassMap: Record<string, string> = {
  'com.fibaro.floodSensor': 'moisture',
  'com.fibaro.motionSensor': 'motion',
  'com.fibaro.doorSensor': 'door',
  'com.fibaro.doorWindowSensor': 'door',
  'com.fibaro.windowSensor': 'window',
  'com.fibaro.smokeSensor': 'smoke',
  'com.fibaro.FGMS001': 'motion',
  'com.fibaro.heatDetector': 'heat',
  'com.fibaro.accelerometer': 'moving',
  'com.fibaro.doorLock': 'lock',
};

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

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

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

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

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

    const info = recordValue(rawDataArg.info) || recordValue(rawDataArg.hub) || recordValue(rawDataArg.controller) || {};
    const devices = recordsValue(rawDataArg.devices ?? rawDataArg.fibaro_devices);
    const scenes = recordsValue(rawDataArg.scenes);
    if (!devices.length && !scenes.length && !Object.keys(info).length) {
      return rawDataArg;
    }

    const host = configArg.host || hostFromUrl(configArg.url) || stringValue(rawDataArg.host) || stringValue(info.host);
    const serialNumber = stringValue(info.serial_number) || stringValue(info.serialNumber) || configArg.uniqueId || host || fibaroProfile.domain;
    const name = configArg.name || stringValue(info.hc_name) || stringValue(info.name) || fibaroDefaultName;
    const entities = [
      ...devices.flatMap((deviceArg) => this.entitiesFromDevice(serialNumber, name, deviceArg)),
      ...scenes.map((sceneArg) => this.entityFromScene(serialNumber, sceneArg)).filter((entityArg): entityArg is ISimpleLocalEntitySnapshot => Boolean(entityArg)),
    ];

    return {
      device: {
        id: serialNumber,
        name,
        manufacturer: stringValue(info.manufacturer_name) || fibaroProfile.manufacturer,
        model: stringValue(info.model_name) || fibaroProfile.model,
        serialNumber,
        host,
        protocol: fibaroProfile.defaultProtocol,
        configurationUrl: host ? `http://${host}` : undefined,
        attributes: {
          currentVersion: stringValue(info.current_version) || stringValue(info.currentVersion),
          macAddress: stringValue(info.mac_address) || stringValue(info.macAddress),
          importPlugins: configArg.importPlugins ?? configArg.import_plugins,
        },
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }

  private static entitiesFromDevice(hubSerialArg: string, hubNameArg: string, deviceArg: Record<string, unknown>): ISimpleLocalEntitySnapshot[] {
    const id = idValue(deviceArg.fibaro_id) || idValue(deviceArg.id);
    if (!id) {
      return [];
    }

    const properties = recordValue(deviceArg.properties) || {};
    const type = stringValue(deviceArg.type);
    const baseType = stringValue(deviceArg.base_type) || stringValue(deviceArg.baseType);
    const platform = platformFromDevice(type, baseType, properties, arrayValue(deviceArg.actions));
    const roomName = stringValue(deviceArg.room_name) || stringValue(deviceArg.roomName);
    const name = stringValue(deviceArg.friendly_name) || [roomName, stringValue(deviceArg.name)].filter(Boolean).join(' ') || `${hubNameArg} ${id}`;
    const value = normalizedState(platform, rawDeviceValue(deviceArg, properties));
    const available = !booleanValue(deviceArg.dead ?? properties.dead, false);
    const entities: ISimpleLocalEntitySnapshot[] = [{
      id: `device_${SimpleLocalMapper.slug(id)}`,
      uniqueId: `${fibaroProfile.domain}_${SimpleLocalMapper.slug(hubSerialArg)}_${SimpleLocalMapper.slug(id)}`,
      name,
      platform,
      state: value,
      available,
      writable: false,
      unit: unitValue(deviceArg.unit ?? properties.unit),
      deviceClass: platform === 'sensor' ? sensorDeviceClassMap[type || ''] || sensorDeviceClassMap[baseType || ''] : platform === 'binary_sensor' ? binarySensorDeviceClassMap[type || ''] || binarySensorDeviceClassMap[baseType || ''] : undefined,
      stateClass: platform === 'sensor' && typeof value === 'number' ? 'measurement' : undefined,
      attributes: {
        fibaroId: id,
        type,
        baseType,
        roomName,
        visible: deviceArg.visible,
        parentFibaroId: deviceArg.parent_fibaro_id ?? deviceArg.parentFibaroId,
      },
    }];

    for (const key of ['power', 'energy']) {
      const propertyValue = properties[key];
      if (propertyValue === undefined) {
        continue;
      }
      entities.push({
        id: `device_${SimpleLocalMapper.slug(id)}_${key}`,
        uniqueId: `${fibaroProfile.domain}_${SimpleLocalMapper.slug(hubSerialArg)}_${SimpleLocalMapper.slug(id)}_${key}`,
        name: `${name} ${titleCase(key)}`,
        platform: 'sensor',
        state: numericOrString(propertyValue),
        available,
        writable: false,
        unit: key === 'power' ? 'W' : 'kWh',
        deviceClass: key,
        stateClass: key === 'energy' ? 'total_increasing' : 'measurement',
        attributes: {
          fibaroId: id,
          sourceProperty: key,
        },
      });
    }

    return entities;
  }

  private static entityFromScene(hubSerialArg: string, sceneArg: Record<string, unknown>): ISimpleLocalEntitySnapshot | undefined {
    const id = idValue(sceneArg.fibaro_id) || idValue(sceneArg.id);
    if (!id) {
      return undefined;
    }

    return {
      id: `scene_${SimpleLocalMapper.slug(id)}`,
      uniqueId: `${fibaroProfile.domain}_${SimpleLocalMapper.slug(hubSerialArg)}_scene_${SimpleLocalMapper.slug(id)}`,
      name: stringValue(sceneArg.name) || `Scene ${id}`,
      platform: 'button',
      state: 'available',
      available: booleanValue(sceneArg.visible, true),
      writable: false,
      attributes: {
        fibaroId: id,
        serviceDomain: 'scene',
        roomId: sceneArg.room_id ?? sceneArg.roomId,
      },
    };
  }
}

const platformFromDevice = (typeArg: string | undefined, baseTypeArg: string | undefined, propertiesArg: Record<string, unknown>, actionsArg: unknown[]): TEntityPlatform => {
  const mapped = (typeArg && fibaroTypePlatformMap[typeArg]) || (baseTypeArg && fibaroTypePlatformMap[baseTypeArg]);
  if (mapped === 'switch' && propertiesArg.isLight === true) {
    return 'light';
  }
  if (mapped) {
    return mapped;
  }
  const haystack = [typeArg, baseTypeArg].filter(Boolean).join(' ').toLowerCase();
  if (haystack.includes('temperature') || haystack.includes('humidity') || haystack.includes('lightSensor'.toLowerCase())) {
    return 'sensor';
  }
  if (haystack.includes('motion') || haystack.includes('door') || haystack.includes('window') || haystack.includes('smoke') || haystack.includes('flood') || haystack.includes('binary')) {
    return 'binary_sensor';
  }
  if (actionsArg.includes('setBrightness') || actionsArg.includes('setColor')) {
    return 'light';
  }
  if (actionsArg.includes('open')) {
    return 'cover';
  }
  if (actionsArg.includes('turnOn')) {
    return 'switch';
  }
  if (actionsArg.includes('secure')) {
    return 'binary_sensor';
  }
  return booleanState(propertiesArg.value) !== undefined ? 'binary_sensor' : 'sensor';
};

const rawDeviceValue = (deviceArg: Record<string, unknown>, propertiesArg: Record<string, unknown>): unknown => propertiesArg.value ?? valueContainer(deviceArg.value) ?? deviceArg.state ?? propertiesArg.state ?? propertiesArg.power ?? propertiesArg.energy ?? null;

const normalizedState = (platformArg: TEntityPlatform, valueArg: unknown): unknown => {
  if (platformArg === 'binary_sensor' || platformArg === 'switch' || platformArg === 'light') {
    return booleanState(valueArg) ?? Boolean(valueArg);
  }
  return numericOrString(valueArg);
};

const booleanState = (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', 'on', 'open', 'opened', 'locked', 'secure', 'yes'].includes(value)) {
    return true;
  }
  if (['0', 'false', 'off', 'closed', 'unlocked', 'unsecure', 'no'].includes(value)) {
    return false;
  }
  return undefined;
};

const booleanValue = (valueArg: unknown, defaultArg: boolean): boolean => booleanState(valueArg) ?? defaultArg;

const numericOrString = (valueArg: unknown): unknown => {
  if (typeof valueArg === 'number' || typeof valueArg === 'boolean' || valueArg === null) {
    return valueArg;
  }
  if (typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg))) {
    return Number(valueArg);
  }
  return valueArg;
};

const valueContainer = (valueArg: unknown): unknown => isRecord(valueArg) ? valueArg.value ?? valueArg.state : valueArg;

const unitValue = (valueArg: unknown): string | undefined => stringValue(valueArg);

const hostFromUrl = (valueArg: unknown): string | undefined => {
  const value = stringValue(valueArg);
  if (!value) {
    return undefined;
  }
  try {
    return new URL(value).hostname;
  } catch {
    return undefined;
  }
};

const idValue = (valueArg: unknown): string | undefined => typeof valueArg === 'number' || typeof valueArg === 'string' ? String(valueArg) : undefined;

const recordsValue = (valueArg: unknown): Array<Record<string, unknown>> => Array.isArray(valueArg) ? valueArg.filter(isRecord) : [];

const arrayValue = (valueArg: unknown): unknown[] => Array.isArray(valueArg) ? 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 titleCase = (valueArg: string): string => valueArg.replace(/[_-]+/g, ' ').replace(/\b\w/g, (charArg) => charArg.toUpperCase());
