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 { IHomeeConfig } from './homee.types.js';
import { homeeDefaultName, homeeProfile } from './homee.types.js';

const supportedPlatforms: TEntityPlatform[] = ['binary_sensor', 'button', 'climate', 'cover', 'fan', 'light', 'number', 'select', 'sensor', 'switch'];

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

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

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

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

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

    const settings = recordValue(rawDataArg.settings) || recordValue(rawDataArg.homee) || rawDataArg;
    const nodes = recordsValue(rawDataArg.nodes);
    if (!nodes.length && !recordValue(rawDataArg.settings)) {
      return rawDataArg;
    }

    const host = configArg.host || stringValue(rawDataArg.host) || stringValue(settings.host);
    const uid = configArg.uniqueId || stringValue(settings.uid) || stringValue(settings.id) || host || homeeProfile.domain;
    const name = configArg.name || stringValue(settings.homee_name ?? settings.homeeName ?? rawDataArg.name) || homeeDefaultName;
    const entities = nodes.flatMap((nodeArg) => nodeEntities(uid, nodeArg));
    if (!entities.length) {
      entities.push({
        id: 'connected',
        uniqueId: `${homeeProfile.domain}_${SimpleLocalMapper.slug(uid)}_connected`,
        name: 'Connected',
        platform: 'binary_sensor',
        state: booleanValue(rawDataArg.connected) ?? true,
        available: true,
        writable: false,
        deviceClass: 'connectivity',
      });
    }

    return {
      device: {
        id: uid,
        name,
        manufacturer: homeeProfile.manufacturer,
        model: homeeProfile.model,
        serialNumber: uid,
        host,
        protocol: homeeProfile.defaultProtocol,
        configurationUrl: host ? `http://${host}` : undefined,
        attributes: {
          macAddress: stringValue(settings.mac_address ?? settings.macAddress),
          version: stringValue(settings.version),
          nodeCount: nodes.length,
          connected: booleanValue(rawDataArg.connected),
        },
      },
      entities,
      online: configArg.online ?? booleanValue(rawDataArg.connected) ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const nodeEntities = (hubUidArg: string, nodeArg: Record<string, unknown>): ISimpleLocalEntitySnapshot[] => {
  const nodeId = idValue(nodeArg.id);
  if (!nodeId) {
    return [];
  }
  const nodeName = stringValue(nodeArg.name) || `Node ${nodeId}`;
  return recordsValue(nodeArg.attributes)
    .map((attributeArg) => attributeEntity(hubUidArg, nodeId, nodeName, nodeArg, attributeArg))
    .filter((entityArg): entityArg is ISimpleLocalEntitySnapshot => Boolean(entityArg));
};

const attributeEntity = (hubUidArg: string, nodeIdArg: string, nodeNameArg: string, nodeArg: Record<string, unknown>, attributeArg: Record<string, unknown>): ISimpleLocalEntitySnapshot | undefined => {
  const attributeId = idValue(attributeArg.id);
  const attributeName = stringValue(attributeArg.name ?? attributeArg.type_name ?? attributeArg.typeName ?? attributeArg.type) || (attributeId ? `Attribute ${attributeId}` : undefined);
  if (!attributeName) {
    return undefined;
  }
  const state = attributeValue(attributeArg);
  const platform = platformForAttribute(nodeArg, attributeArg, state);
  const writable = booleanValue(attributeArg.writable ?? attributeArg.editable ?? attributeArg.is_editable ?? attributeArg.isEditable ?? attributeArg.canSet) ?? isWritablePlatform(platform);
  const nodeSlug = SimpleLocalMapper.slug(nodeNameArg);
  const attributeSlug = SimpleLocalMapper.slug(attributeName);

  return {
    id: `${nodeSlug}_${attributeSlug}`,
    uniqueId: `${homeeProfile.domain}_${SimpleLocalMapper.slug(hubUidArg)}_${SimpleLocalMapper.slug(nodeIdArg)}_${SimpleLocalMapper.slug(attributeId || attributeName)}`,
    name: `${nodeNameArg} ${attributeName}`,
    platform,
    state: platform === 'button' && state === undefined ? 'available' : state,
    available: isAvailable(nodeArg, attributeArg),
    writable,
    unit: unitValue(attributeArg.unit),
    deviceClass: deviceClassForAttribute(platform, nodeArg, attributeArg),
    stateClass: platform === 'sensor' && typeof state === 'number' ? 'measurement' : undefined,
    attributes: {
      nodeId: nodeIdArg,
      attributeId,
      nodeProfile: stringValue(nodeArg.profileName ?? nodeArg.profile),
      homeeType: stringValue(attributeArg.typeName ?? attributeArg.type_name ?? attributeArg.type),
      instance: attributeArg.instance,
      editable: writable,
    },
  };
};

const platformForAttribute = (nodeArg: Record<string, unknown>, attributeArg: Record<string, unknown>, stateArg: unknown): TEntityPlatform => {
  const explicit = stringValue(attributeArg.platform);
  if (explicit && supportedPlatforms.includes(explicit as TEntityPlatform)) {
    return explicit as TEntityPlatform;
  }
  const text = [attributeArg.type, attributeArg.typeName, attributeArg.type_name, attributeArg.name, nodeArg.profile, nodeArg.profileName].filter(Boolean).join(' ').toLowerCase();
  const writable = booleanValue(attributeArg.writable ?? attributeArg.editable ?? attributeArg.is_editable ?? attributeArg.isEditable ?? attributeArg.canSet) ?? false;
  if (text.includes('button') || text.includes('impulse')) {
    return 'button';
  }
  if (text.includes('thermostat') || text.includes('target_temperature') || text.includes('target temperature')) {
    return 'climate';
  }
  if (text.includes('blind') || text.includes('shutter') || text.includes('cover') || text.includes('slat')) {
    return 'cover';
  }
  if (text.includes('fan') || text.includes('ventilation')) {
    return 'fan';
  }
  if (text.includes('light') || text.includes('dimming') || text.includes('brightness')) {
    return 'light';
  }
  if (Array.isArray(attributeArg.options)) {
    return 'select';
  }
  if (writable && typeof stateArg === 'number') {
    return 'number';
  }
  if (writable && (text.includes('switch') || text.includes('on_off') || typeof stateArg === 'boolean')) {
    return 'switch';
  }
  if (typeof stateArg === 'boolean' || ['alarm', 'contact', 'door', 'flood', 'leak', 'motion', 'presence', 'smoke', 'window'].some((keywordArg) => text.includes(keywordArg))) {
    return 'binary_sensor';
  }
  return 'sensor';
};

const deviceClassForAttribute = (platformArg: TEntityPlatform, nodeArg: Record<string, unknown>, attributeArg: Record<string, unknown>): string | undefined => {
  const text = [attributeArg.type, attributeArg.typeName, attributeArg.type_name, attributeArg.name, nodeArg.profile, nodeArg.profileName].filter(Boolean).join(' ').toLowerCase();
  if (platformArg === 'binary_sensor') {
    if (text.includes('motion') || text.includes('presence')) {
      return 'motion';
    }
    if (text.includes('door')) {
      return 'door';
    }
    if (text.includes('window')) {
      return 'window';
    }
    if (text.includes('flood') || text.includes('leak') || text.includes('water')) {
      return 'moisture';
    }
    if (text.includes('smoke')) {
      return 'smoke';
    }
    if (text.includes('alarm') || text.includes('malfunction') || text.includes('overload')) {
      return 'problem';
    }
    return undefined;
  }
  if (platformArg !== 'sensor') {
    return undefined;
  }
  if (text.includes('temperature')) {
    return 'temperature';
  }
  if (text.includes('humidity')) {
    return 'humidity';
  }
  if (text.includes('brightness') || text.includes('illuminance') || text.includes('lux')) {
    return 'illuminance';
  }
  if (text.includes('power')) {
    return 'power';
  }
  if (text.includes('energy')) {
    return 'energy';
  }
  if (text.includes('battery')) {
    return 'battery';
  }
  if (text.includes('voltage')) {
    return 'voltage';
  }
  if (text.includes('current')) {
    return 'current';
  }
  return undefined;
};

const attributeValue = (attributeArg: Record<string, unknown>): unknown => numericOrBoolean(attributeArg.current_value ?? attributeArg.currentValue ?? attributeArg.value ?? attributeArg.target_value ?? attributeArg.targetValue ?? attributeArg.last_value ?? attributeArg.lastValue);

const isAvailable = (nodeArg: Record<string, unknown>, attributeArg: Record<string, unknown>): boolean => {
  const nodeState = stringValue(nodeArg.state)?.toLowerCase();
  const attributeState = stringValue(attributeArg.state)?.toLowerCase();
  if (nodeState && ['unavailable', 'offline', 'error'].includes(nodeState)) {
    return false;
  }
  if (attributeState && ['unavailable', 'offline', 'error'].includes(attributeState)) {
    return false;
  }
  return true;
};

const isWritablePlatform = (platformArg: TEntityPlatform): boolean => ['button', 'climate', 'cover', 'fan', 'light', 'number', 'select', 'switch'].includes(platformArg);

const unitValue = (valueArg: unknown): string | undefined => {
  const value = stringValue(valueArg);
  if (!value || ['', 'n/a', 'text'].includes(value.toLowerCase())) {
    return undefined;
  }
  return value;
};

const numericOrBoolean = (valueArg: unknown): unknown => {
  if (typeof valueArg === 'number' || typeof valueArg === 'boolean' || valueArg === null || valueArg === undefined) {
    return valueArg;
  }
  if (typeof valueArg === 'string') {
    const lower = valueArg.toLowerCase();
    if (['true', 'on', 'open', '1'].includes(lower)) {
      return true;
    }
    if (['false', 'off', 'closed', '0'].includes(lower)) {
      return false;
    }
    if (valueArg.trim() && Number.isFinite(Number(valueArg))) {
      return Number(valueArg);
    }
  }
  return valueArg;
};

const booleanValue = (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', 'yes', 'connected'].includes(value)) {
    return true;
  }
  if (['0', 'false', 'off', 'no', 'disconnected'].includes(value)) {
    return false;
  }
  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 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;
