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 { IFlexitBacnetConfig } from './flexit_bacnet.types.js';
import { flexitBacnetDefaultDeviceId, flexitBacnetDefaultName, flexitBacnetProfile } from './flexit_bacnet.types.js';

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

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

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

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

  private static normalizeRawData(configArg: IFlexitBacnetConfig, rawDataArg: unknown): unknown {
    if (isRecord(rawDataArg) && 'device' in rawDataArg && 'entities' in rawDataArg) {
      return rawDataArg;
    }
    const rawObject = isRecord(rawDataArg) ? rawDataArg : undefined;
    if (!rawObject || Array.isArray(rawObject.entities)) {
      return rawDataArg;
    }

    const metadata = configArg.metadata || {};
    const host = stringValue(configArg.ipAddress) || stringValue(configArg.ip_address) || configArg.host || stringValue(rawObject.ip_address) || stringValue(rawObject.ipAddress) || stringValue(rawObject.host) || stringValue(metadata.ipAddress) || stringValue(metadata.ip_address);
    const deviceId = numberValue(configArg.deviceId ?? configArg.device_id ?? rawObject.device_id ?? rawObject.deviceId ?? metadata.deviceId ?? metadata.device_id) ?? flexitBacnetDefaultDeviceId;
    const serialNumber = stringValue(rawObject.serial_number) || stringValue(rawObject.serialNumber) || stringValue(rawObject.serial) || configArg.uniqueId || (host ? `${host}:${deviceId}` : undefined);
    const name = configArg.name || stringValue(rawObject.device_name) || stringValue(rawObject.deviceName) || stringValue(rawObject.name) || flexitBacnetDefaultName;
    const uniqueBase = SimpleLocalMapper.slug(serialNumber || name);
    const entities: ISimpleLocalEntitySnapshot[] = [];

    const addEntity = (entityArg: ISimpleLocalEntitySnapshot | undefined) => {
      if (entityArg) {
        entities.push(entityArg);
      }
    };

    addEntity(climateEntity(rawObject, uniqueBase));
    for (const description of sensorDescriptions) {
      addEntity(valueEntity(rawObject, uniqueBase, description));
    }
    for (const description of binarySensorDescriptions) {
      addEntity(valueEntity(rawObject, uniqueBase, description));
    }
    for (const description of switchDescriptions) {
      addEntity(valueEntity(rawObject, uniqueBase, description));
    }
    for (const description of numberDescriptions) {
      addEntity(valueEntity(rawObject, uniqueBase, description));
    }

    return {
      device: {
        id: configArg.uniqueId || serialNumber || `${host || flexitBacnetProfile.domain}:${deviceId}`,
        name,
        manufacturer: flexitBacnetProfile.manufacturer,
        model: stringValue(rawObject.model) || 'Nordic',
        serialNumber,
        host,
        protocol: flexitBacnetProfile.defaultProtocol,
        attributes: {
          deviceId,
          modelId: rawObject.model_id ?? rawObject.modelId,
          ipAddress: host,
        },
      },
      entities: entities.length ? entities : [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: true, writable: false }],
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

interface IEntityDescription {
  id: string;
  name: string;
  platform: ISimpleLocalEntitySnapshot['platform'];
  keys: string[];
  unit?: string;
  deviceClass?: string;
  stateClass?: string;
  writable?: boolean;
  attributes?: Record<string, unknown>;
}

const sensorDescriptions: IEntityDescription[] = [
  { id: 'outside_air_temperature', name: 'Outside air temperature', platform: 'sensor', keys: ['outside_air_temperature', 'outsideAirTemperature'], unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  { id: 'supply_air_temperature', name: 'Supply air temperature', platform: 'sensor', keys: ['supply_air_temperature', 'supplyAirTemperature'], unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  { id: 'exhaust_air_temperature', name: 'Exhaust air temperature', platform: 'sensor', keys: ['exhaust_air_temperature', 'exhaustAirTemperature'], unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  { id: 'extract_air_temperature', name: 'Extract air temperature', platform: 'sensor', keys: ['extract_air_temperature', 'extractAirTemperature'], unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  { id: 'room_temperature', name: 'Room temperature', platform: 'sensor', keys: ['room_temperature', 'roomTemperature'], unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' },
  { id: 'fireplace_ventilation_remaining_duration', name: 'Fireplace ventilation remaining duration', platform: 'sensor', keys: ['fireplace_ventilation_remaining_duration', 'fireplaceVentilationRemainingDuration'], unit: 'min', deviceClass: 'duration', stateClass: 'measurement' },
  { id: 'rapid_ventilation_remaining_duration', name: 'Rapid ventilation remaining duration', platform: 'sensor', keys: ['rapid_ventilation_remaining_duration', 'rapidVentilationRemainingDuration'], unit: 'min', deviceClass: 'duration', stateClass: 'measurement' },
  { id: 'supply_air_fan_control_signal', name: 'Supply air fan control signal', platform: 'sensor', keys: ['supply_air_fan_control_signal', 'supplyAirFanControlSignal'], unit: '%', stateClass: 'measurement' },
  { id: 'supply_air_fan_rpm', name: 'Supply air fan RPM', platform: 'sensor', keys: ['supply_air_fan_rpm', 'supplyAirFanRpm'], unit: 'rpm', stateClass: 'measurement' },
  { id: 'exhaust_air_fan_control_signal', name: 'Exhaust air fan control signal', platform: 'sensor', keys: ['exhaust_air_fan_control_signal', 'exhaustAirFanControlSignal'], unit: '%', stateClass: 'measurement' },
  { id: 'exhaust_air_fan_rpm', name: 'Exhaust air fan RPM', platform: 'sensor', keys: ['exhaust_air_fan_rpm', 'exhaustAirFanRpm'], unit: 'rpm', stateClass: 'measurement' },
  { id: 'electric_heater_power', name: 'Electric heater power', platform: 'sensor', keys: ['electric_heater_power', 'electricHeaterPower'], unit: 'kW', deviceClass: 'power' },
  { id: 'air_filter_operating_time', name: 'Air filter operating time', platform: 'sensor', keys: ['air_filter_operating_time', 'airFilterOperatingTime'], unit: 'h', stateClass: 'total_increasing' },
  { id: 'heat_exchanger_efficiency', name: 'Heat exchanger efficiency', platform: 'sensor', keys: ['heat_exchanger_efficiency', 'heatExchangerEfficiency'], unit: '%', stateClass: 'measurement' },
  { id: 'heat_exchanger_speed', name: 'Heat exchanger speed', platform: 'sensor', keys: ['heat_exchanger_speed', 'heatExchangerSpeed'], unit: '%', stateClass: 'measurement' },
];

const binarySensorDescriptions: IEntityDescription[] = [
  { id: 'air_filter_polluted', name: 'Air filter polluted', platform: 'binary_sensor', keys: ['air_filter_polluted', 'airFilterPolluted'], deviceClass: 'problem', writable: false },
];

const switchDescriptions: IEntityDescription[] = [
  { id: 'electric_heater', name: 'Electric heater', platform: 'switch', keys: ['electric_heater', 'electricHeater'], writable: true },
  { id: 'cooker_hood_mode', name: 'Cooker hood mode', platform: 'switch', keys: ['cooker_hood_status', 'cookerHoodStatus', 'cooker_hood_mode', 'cookerHoodMode'], writable: true },
  { id: 'fireplace_mode', name: 'Fireplace mode', platform: 'switch', keys: ['fireplace_ventilation_status', 'fireplaceVentilationStatus', 'fireplace_mode', 'fireplaceMode'], writable: true, attributes: { deprecatedInHomeAssistant: '2026.9.0' } },
];

const numberDescriptions: IEntityDescription[] = [
  { id: 'away_extract_fan_setpoint', name: 'Away extract fan setpoint', platform: 'number', keys: ['fan_setpoint_extract_air_away', 'away_extract_fan_setpoint'], unit: '%', writable: true },
  { id: 'away_supply_fan_setpoint', name: 'Away supply fan setpoint', platform: 'number', keys: ['fan_setpoint_supply_air_away', 'away_supply_fan_setpoint'], unit: '%', writable: true },
  { id: 'home_extract_fan_setpoint', name: 'Home extract fan setpoint', platform: 'number', keys: ['fan_setpoint_extract_air_home', 'home_extract_fan_setpoint'], unit: '%', writable: true },
  { id: 'home_supply_fan_setpoint', name: 'Home supply fan setpoint', platform: 'number', keys: ['fan_setpoint_supply_air_home', 'home_supply_fan_setpoint'], unit: '%', writable: true },
  { id: 'high_extract_fan_setpoint', name: 'High extract fan setpoint', platform: 'number', keys: ['fan_setpoint_extract_air_high', 'high_extract_fan_setpoint'], unit: '%', writable: true },
  { id: 'high_supply_fan_setpoint', name: 'High supply fan setpoint', platform: 'number', keys: ['fan_setpoint_supply_air_high', 'high_supply_fan_setpoint'], unit: '%', writable: true },
  { id: 'cooker_hood_extract_fan_setpoint', name: 'Cooker hood extract fan setpoint', platform: 'number', keys: ['fan_setpoint_extract_air_cooker', 'cooker_hood_extract_fan_setpoint'], unit: '%', writable: true },
  { id: 'cooker_hood_supply_fan_setpoint', name: 'Cooker hood supply fan setpoint', platform: 'number', keys: ['fan_setpoint_supply_air_cooker', 'cooker_hood_supply_fan_setpoint'], unit: '%', writable: true },
  { id: 'fireplace_extract_fan_setpoint', name: 'Fireplace extract fan setpoint', platform: 'number', keys: ['fan_setpoint_extract_air_fire', 'fireplace_extract_fan_setpoint'], unit: '%', writable: true },
  { id: 'fireplace_supply_fan_setpoint', name: 'Fireplace supply fan setpoint', platform: 'number', keys: ['fan_setpoint_supply_air_fire', 'fireplace_supply_fan_setpoint'], unit: '%', writable: true },
  { id: 'fireplace_mode_runtime', name: 'Fireplace mode runtime', platform: 'number', keys: ['fireplace_mode_runtime', 'fireplaceModeRuntime'], unit: 'min', deviceClass: 'duration', writable: true },
];

const climateEntity = (rawObjectArg: Record<string, unknown>, uniqueBaseArg: string): ISimpleLocalEntitySnapshot | undefined => {
  const currentTemperature = rawValue(rawObjectArg, ['room_temperature', 'roomTemperature']);
  const ventilationMode = stringValue(rawValue(rawObjectArg, ['ventilation_mode', 'ventilationMode']));
  const operationMode = stringValue(rawValue(rawObjectArg, ['operation_mode', 'operationMode']));
  const targetTemperature = ventilationMode === 'away'
    ? rawValue(rawObjectArg, ['air_temp_setpoint_away', 'airTempSetpointAway'])
    : rawValue(rawObjectArg, ['air_temp_setpoint_home', 'airTempSetpointHome']);
  if (currentTemperature === undefined && targetTemperature === undefined && !ventilationMode && !operationMode) {
    return undefined;
  }
  return {
    id: 'climate',
    uniqueId: `${flexitBacnetProfile.domain}_${uniqueBaseArg}_climate`,
    name: 'Climate',
    platform: 'climate',
    state: operationMode?.toLowerCase().includes('off') ? 'off' : 'fan_only',
    writable: true,
    attributes: {
      currentTemperature,
      targetTemperature,
      hvacAction: truthy(rawValue(rawObjectArg, ['electric_heater', 'electricHeater'])) ? 'heating' : 'fan',
      hvacModes: ['off', 'fan_only'],
      presetMode: presetMode(operationMode),
      presetModes: ['away', 'home', 'high', 'fireplace'],
      ventilationMode,
      minTemp: 10,
      maxTemp: 30,
      temperatureUnit: 'C',
    },
  };
};

const valueEntity = (rawObjectArg: Record<string, unknown>, uniqueBaseArg: string, descriptionArg: IEntityDescription): ISimpleLocalEntitySnapshot | undefined => {
  const state = rawValue(rawObjectArg, descriptionArg.keys);
  if (state === undefined) {
    return undefined;
  }
  return {
    id: descriptionArg.id,
    uniqueId: `${flexitBacnetProfile.domain}_${uniqueBaseArg}_${descriptionArg.id}`,
    name: descriptionArg.name,
    platform: descriptionArg.platform,
    state,
    unit: descriptionArg.unit,
    deviceClass: descriptionArg.deviceClass,
    stateClass: descriptionArg.stateClass,
    writable: descriptionArg.writable ?? false,
    attributes: descriptionArg.attributes,
  };
};

const presetMode = (operationModeArg: string | undefined): string | undefined => {
  const value = operationModeArg?.toLowerCase();
  if (!value) {
    return undefined;
  }
  if (value.includes('away')) return 'away';
  if (value.includes('home')) return 'home';
  if (value.includes('high')) return 'high';
  if (value.includes('fireplace')) return 'fireplace';
  return 'none';
};

const rawValue = (rawObjectArg: Record<string, unknown>, keysArg: string[]): unknown => {
  for (const key of keysArg) {
    if (rawObjectArg[key] !== undefined) {
      return rawObjectArg[key];
    }
  }
  return undefined;
};

const truthy = (valueArg: unknown): boolean => valueArg === true || valueArg === 'true' || valueArg === 1 || valueArg === 'on';

const numberValue = (valueArg: unknown): number | undefined => {
  const value = typeof valueArg === 'number' ? valueArg : typeof valueArg === 'string' && valueArg.trim() ? Number(valueArg) : undefined;
  return value !== undefined && Number.isFinite(value) ? Math.round(value) : 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;
