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 { IMaxcubeConfig, IMaxcubeDevice, IMaxcubeRawState } from './maxcube.types.js';
import { maxcubeProfile } from './maxcube.types.js';

export class MaxcubeMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IMaxcubeConfig>, 'profile'>): ISimpleLocalSnapshot {
    const rawState = maxcubeRawState(optionsArg.rawData) || maxcubeRawState(optionsArg.config.rawData);
    if (rawState) {
      return maxcubeSnapshot(optionsArg.config, rawState, optionsArg.online ?? true, optionsArg.source || 'tcp', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: maxcubeProfile });
  }

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

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

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

  public static clone<TValue>(valueArg: TValue): TValue {
    return SimpleLocalMapper.clone(valueArg);
  }
}

const maxcubeSnapshot = (configArg: IMaxcubeConfig, rawDataArg: IMaxcubeRawState, onlineArg: boolean, sourceArg: ISimpleLocalSnapshot['source'], errorArg?: string): ISimpleLocalSnapshot => {
  const endpointHost = rawDataArg.cube.host || configArg.host || hostFromUrl(configArg.url);
  const endpointPort = rawDataArg.cube.port || configArg.port || maxcubeProfile.defaultPort;
  const deviceName = configArg.name || rawDataArg.cube.serial || maxcubeProfile.defaultName || maxcubeProfile.displayName;
  return {
    device: {
      id: configArg.uniqueId || rawDataArg.cube.serial || rawDataArg.cube.rfAddress || (endpointHost ? `${endpointHost}:${endpointPort || ''}` : maxcubeProfile.domain),
      name: deviceName,
      manufacturer: maxcubeProfile.manufacturer,
      model: 'MAX! Cube LAN Gateway',
      serialNumber: rawDataArg.cube.serial,
      host: endpointHost,
      port: endpointPort,
      protocol: 'tcp',
      configurationUrl: endpointHost ? `tcp://${endpointHost}${endpointPort ? `:${endpointPort}` : ''}` : undefined,
      attributes: cleanAttributes({ firmwareVersion: rawDataArg.cube.firmwareVersion, rfAddress: rawDataArg.cube.rfAddress, ...configArg.metadata }),
    },
    entities: rawDataArg.devices.flatMap((deviceArg) => entitiesForDevice(configArg, rawDataArg, deviceArg, onlineArg)),
    online: onlineArg,
    updatedAt: rawDataArg.fetchedAt || new Date().toISOString(),
    source: sourceArg,
    rawData: rawDataArg,
    error: errorArg,
    capabilities: {
      localRead: true,
      localControl: Boolean(configArg.host || configArg.url || configArg.client?.execute || configArg.commandExecutor),
      maxcubeTcp: Boolean(configArg.host || configArg.url),
    },
  };
};

const entitiesForDevice = (configArg: IMaxcubeConfig, rawDataArg: IMaxcubeRawState, deviceArg: IMaxcubeDevice, onlineArg: boolean): ISimpleLocalEntitySnapshot[] => {
  const roomName = deviceArg.roomName || rawDataArg.rooms.find((roomArg) => roomArg.id === deviceArg.roomId)?.name;
  const baseName = [roomName, deviceArg.name].filter(Boolean).join(' ') || deviceArg.serial || deviceArg.rfAddress;
  const baseId = SimpleLocalMapper.slug(deviceArg.serial || deviceArg.rfAddress || baseName);
  const writable = Boolean(configArg.host || configArg.url || configArg.client?.execute || configArg.commandExecutor);
  const entities: ISimpleLocalEntitySnapshot[] = [];

  if (deviceArg.kind === 'thermostat' || deviceArg.kind === 'wall_thermostat') {
    entities.push({
      id: `${baseId}_climate`,
      uniqueId: deviceArg.serial || `${deviceArg.rfAddress}_climate`,
      name: baseName,
      platform: 'climate',
      state: hvacMode(deviceArg),
      available: onlineArg,
      writable,
      unit: 'C',
      attributes: cleanAttributes({
        rfAddress: deviceArg.rfAddress,
        serial: deviceArg.serial,
        roomId: deviceArg.roomId,
        roomName,
        kind: deviceArg.kind,
        mode: deviceArg.mode,
        modeName: deviceArg.modeName,
        hvacMode: hvacMode(deviceArg),
        presetMode: presetMode(deviceArg),
        currentTemperature: deviceArg.actualTemperature,
        targetTemperature: deviceArg.targetTemperature,
        comfortTemperature: deviceArg.comfortTemperature,
        ecoTemperature: deviceArg.ecoTemperature,
        minTemperature: deviceArg.minTemperature,
        maxTemperature: deviceArg.maxTemperature,
        valvePosition: deviceArg.valvePosition,
      }),
    });
  }

  if (deviceArg.kind === 'window_shutter') {
    entities.push({
      id: `${baseId}_window`,
      uniqueId: deviceArg.serial || `${deviceArg.rfAddress}_window`,
      name: baseName,
      platform: 'binary_sensor',
      state: Boolean(deviceArg.isOpen),
      available: onlineArg,
      writable: false,
      deviceClass: 'window',
      attributes: cleanAttributes({ rfAddress: deviceArg.rfAddress, serial: deviceArg.serial, roomId: deviceArg.roomId, roomName, initialized: deviceArg.initialized }),
    });
  }

  entities.push({
    id: `${baseId}_battery`,
    uniqueId: `${deviceArg.serial || deviceArg.rfAddress}_battery`,
    name: `${baseName} battery`,
    platform: 'binary_sensor',
    state: Boolean(deviceArg.batteryLow),
    available: onlineArg,
    writable: false,
    deviceClass: 'battery',
    attributes: cleanAttributes({ rfAddress: deviceArg.rfAddress, serial: deviceArg.serial, roomId: deviceArg.roomId, roomName, lowBattery: deviceArg.batteryLow }),
  });

  return entities;
};

const hvacMode = (deviceArg: IMaxcubeDevice): string => {
  if (deviceArg.modeName === 'auto' || deviceArg.modeName === 'boost') {
    return 'auto';
  }
  if (deviceArg.modeName === 'manual' && deviceArg.targetTemperature === 4.5) {
    return 'off';
  }
  return 'heat';
};

const presetMode = (deviceArg: IMaxcubeDevice): string => {
  if (deviceArg.modeName === 'boost') {
    return 'boost';
  }
  if (deviceArg.modeName === 'away') {
    return 'away';
  }
  if (deviceArg.modeName === 'manual' && deviceArg.targetTemperature === deviceArg.comfortTemperature) {
    return 'comfort';
  }
  if (deviceArg.modeName === 'manual' && deviceArg.targetTemperature === deviceArg.ecoTemperature) {
    return 'eco';
  }
  if (deviceArg.modeName === 'manual' && deviceArg.targetTemperature === 30.5) {
    return 'on';
  }
  return 'none';
};

const maxcubeRawState = (valueArg: unknown): IMaxcubeRawState | undefined => {
  if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
    return undefined;
  }
  const value = valueArg as Partial<IMaxcubeRawState>;
  if (value.cube && Array.isArray(value.rooms) && Array.isArray(value.devices)) {
    return value as IMaxcubeRawState;
  }
  return undefined;
};

const hostFromUrl = (valueArg: string | undefined): string | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    return new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg) ? valueArg : `tcp://${valueArg}`).hostname;
  } catch {
    return valueArg.includes(':') ? valueArg.split(':')[0] : valueArg;
  }
};

const cleanAttributes = (attributesArg: Record<string, unknown>): Record<string, unknown> | undefined => {
  const clean = Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  return Object.keys(clean).length ? clean : undefined;
};
