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 { IEnergeniePowerSocketsConfig } from './energenie_power_sockets.types.js';
import { energeniePowerSocketsDefaultName, energeniePowerSocketsProfile } from './energenie_power_sockets.types.js';

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

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

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

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

  private static normalizeRawData(configArg: IEnergeniePowerSocketsConfig, rawDataArg: unknown): unknown {
    if (isSimpleSnapshot(rawDataArg)) {
      return rawDataArg;
    }

    const rawRecord = recordValue(rawDataArg);
    if (!rawRecord) {
      return rawDataArg;
    }

    const deviceApiId = configArg.deviceApiId || stringValue(configArg['api-device-id']) || stringValue(rawRecord.device_id) || stringValue(rawRecord.deviceId) || stringValue(rawRecord.id);
    const numberOfSockets = configArg.numberOfSockets || numberValue(rawRecord.numberOfSockets) || numberValue(rawRecord.number_of_sockets) || socketCount(rawRecord);
    const sockets = socketEntries(rawRecord, numberOfSockets);
    if (!sockets.length) {
      return rawDataArg;
    }

    const name = configArg.name || stringValue(rawRecord.name) || energeniePowerSocketsDefaultName;
    const manufacturer = stringValue(rawRecord.manufacturer) || energeniePowerSocketsProfile.manufacturer;
    const entities = sockets.map((socketArg) => socketEntity(deviceApiId || name, socketArg));

    return {
      device: {
        id: configArg.uniqueId || deviceApiId || name,
        name,
        manufacturer,
        model: stringValue(rawRecord.model) || stringValue(rawRecord.name) || energeniePowerSocketsProfile.model,
        serialNumber: deviceApiId,
        protocol: energeniePowerSocketsProfile.defaultProtocol,
        attributes: {
          deviceApiId,
          numberOfSockets: sockets.length,
          swVersion: stringValue(rawRecord.sw_version) || stringValue(rawRecord.swVersion),
        },
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

interface ISocketRecord {
  socketId: number;
  state: boolean | null;
  available: boolean;
}

const socketEntity = (deviceIdArg: string, socketArg: ISocketRecord): ISimpleLocalEntitySnapshot => {
  const base = `${SimpleLocalMapper.slug(deviceIdArg)}_${socketArg.socketId}`;
  return {
    id: `socket_${socketArg.socketId}`,
    uniqueId: `${energeniePowerSocketsProfile.domain}_${base}`,
    name: `Socket ${socketArg.socketId}`,
    platform: 'switch',
    state: socketArg.state,
    available: socketArg.available,
    writable: true,
    attributes: {
      socketId: socketArg.socketId,
    },
  };
};

const socketEntries = (rawRecordArg: Record<string, unknown>, numberOfSocketsArg: number | undefined): ISocketRecord[] => {
  const sockets = rawRecordArg.sockets ?? rawRecordArg.socketStates ?? rawRecordArg.states;
  if (Array.isArray(sockets)) {
    return sockets.map((valueArg, indexArg) => toSocketRecord(indexArg, valueArg));
  }
  const socketRecord = recordValue(sockets);
  if (socketRecord) {
    return Object.entries(socketRecord).map(([socketIdArg, valueArg]) => toSocketRecord(numberValue(socketIdArg) ?? 0, valueArg));
  }
  if (numberOfSocketsArg && numberOfSocketsArg > 0) {
    return Array.from({ length: numberOfSocketsArg }, (_valueArg, indexArg) => ({ socketId: indexArg, state: null, available: false }));
  }
  return [];
};

const toSocketRecord = (socketIdArg: number, valueArg: unknown): ISocketRecord => {
  const record = recordValue(valueArg);
  const state = switchState(record ? record.state ?? record.is_on ?? record.isOn ?? record.on ?? record.status : valueArg);
  return {
    socketId: socketIdArg,
    state: state ?? null,
    available: state !== undefined,
  };
};

const socketCount = (rawRecordArg: Record<string, unknown>): number | undefined => {
  const sockets = rawRecordArg.sockets ?? rawRecordArg.socketStates ?? rawRecordArg.states;
  if (Array.isArray(sockets)) {
    return sockets.length;
  }
  const socketRecord = recordValue(sockets);
  return socketRecord ? Object.keys(socketRecord).length : undefined;
};

const switchState = (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 (['on', 'true', '1'].includes(value)) {
    return true;
  }
  if (['off', 'false', '0'].includes(value)) {
    return false;
  }
  return undefined;
};

const isSimpleSnapshot = (valueArg: unknown): valueArg is ISimpleLocalSnapshot => Boolean(recordValue(valueArg)?.device && Array.isArray(recordValue(valueArg)?.entities));
const recordValue = (valueArg: unknown): Record<string, unknown> | undefined => valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : undefined;
const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
const numberValue = (valueArg: unknown): number | undefined => {
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return Math.round(valueArg);
  }
  if (typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg))) {
    return Math.round(Number(valueArg));
  }
  return undefined;
};
