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 { IGeniushubConfig } from './geniushub.types.js';
import { geniushubDefaultName, geniushubProfile } from './geniushub.types.js';

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

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

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

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

  private static normalizeRawData(configArg: IGeniushubConfig, rawDataArg: unknown): unknown {
    if (isSnapshotRecord(rawDataArg)) {
      return rawDataArg;
    }

    const rawObject = isRecord(rawDataArg) ? rawDataArg : undefined;
    const zones = arrayValue(rawObject?.zones ?? rawObject?.zone_objs ?? rawObject?.zoneObjects);
    const devices = arrayValue(rawObject?.devices ?? rawObject?.device_objs ?? rawObject?.deviceObjects);
    const issues = arrayValue(rawObject?.issues);
    if (!zones.length && !devices.length && !issues.length) {
      return rawDataArg;
    }

    const name = configArg.name || stringValue(rawObject?.name) || geniushubDefaultName;
    const uniqueBase = SimpleLocalMapper.slug(configArg.uniqueId || name);
    const entities: ISimpleLocalEntitySnapshot[] = [];

    for (const zone of zones) {
      const zoneData = recordValue(zone.data) || zone;
      const zoneName = stringValue(zone.name) || stringValue(zoneData.name) || `Zone ${stringValue(zone.id) || entities.length + 1}`;
      const zoneType = stringValue(zone.type) || stringValue(zoneData.type) || 'zone';
      const mode = stringValue(zone.mode) || stringValue(zoneData.mode);
      const setpoint = numberValue(zone.setpoint ?? zoneData.setpoint ?? zone.setTemperature ?? zoneData.setTemperature);
      const temperature = numberValue(zone.temperature ?? zoneData.temperature);
      const zoneId = idValue(zone.id) || SimpleLocalMapper.slug(zoneName);
      const entityId = `zone_${SimpleLocalMapper.slug(zoneId)}`;

      if (['radiator', 'wet underfloor'].includes(zoneType)) {
        entities.push({
          id: entityId,
          name: zoneName,
          platform: 'climate',
          state: mode || 'heat',
          available: true,
          writable: true,
          attributes: {
            zoneId,
            zoneType,
            currentTemperature: temperature,
            targetTemperature: setpoint,
            hvacModes: ['off', 'heat'],
            presetModes: zoneData.occupied !== undefined ? ['activity', 'boost'] : ['boost'],
            occupied: zoneData.occupied,
            output: zoneData.output,
          },
        });
        continue;
      }

      if (zoneType === 'hot water temperature') {
        entities.push({
          id: entityId,
          name: zoneName,
          platform: 'climate',
          state: mode || 'auto',
          available: true,
          writable: true,
          attributes: {
            homeAssistantPlatform: 'water_heater',
            zoneId,
            zoneType,
            currentTemperature: temperature,
            targetTemperature: setpoint,
            operationList: ['off', 'auto', 'manual'],
          },
        });
        continue;
      }

      if (zoneType === 'on / off') {
        entities.push({
          id: entityId,
          name: zoneName,
          platform: 'switch',
          state: Boolean((mode === 'override' || mode === 'timer') && setpoint),
          available: true,
          writable: true,
          attributes: {
            zoneId,
            zoneType,
            mode,
          },
        });
      }
    }

    for (const device of devices) {
      const deviceData = recordValue(device.data) || device;
      const state = recordValue(deviceData.state) || recordValue(deviceData._state) || deviceData;
      const deviceId = idValue(device.id) || SimpleLocalMapper.slug(stringValue(device.name) || stringValue(device.type) || 'device');
      const deviceType = stringValue(device.type) || stringValue(deviceData.type) || 'Device';
      const baseName = `${deviceType} ${deviceId}`.trim();
      const batteryLevel = numberValue(state.batteryLevel);
      const outputOnOff = booleanValue(state.outputOnOff) ?? booleanFromNumber(state.outputOnOff);

      if (batteryLevel !== undefined) {
        entities.push({
          id: `device_${SimpleLocalMapper.slug(deviceId)}_battery`,
          name: `${baseName} Battery`,
          platform: 'sensor',
          state: batteryLevel === 255 ? 0 : batteryLevel,
          available: true,
          unit: '%',
          deviceClass: 'battery',
          stateClass: 'measurement',
          attributes: {
            assignedZone: firstAssignedZone(deviceData),
          },
        });
      }
      if (outputOnOff !== undefined || deviceType.includes('Receiver')) {
        entities.push({
          id: `device_${SimpleLocalMapper.slug(deviceId)}_output`,
          name: `${baseName} Output`,
          platform: 'binary_sensor',
          state: outputOnOff ?? false,
          available: true,
          attributes: {
            assignedZone: firstAssignedZone(deviceData),
          },
        });
      }
    }

    for (const level of ['error', 'warning', 'information']) {
      const levelIssues = issues.filter((issueArg) => stringValue(issueArg.level) === level).map((issueArg) => stringValue(issueArg.description)).filter((valueArg): valueArg is string => Boolean(valueArg));
      entities.push({
        id: `${level}_issues`,
        name: `GeniusHub ${titleCase(level)}s`,
        platform: 'sensor',
        state: levelIssues.length,
        available: true,
        attributes: {
          [`${level}_list`]: levelIssues,
        },
      });
    }

    return {
      device: {
        id: configArg.uniqueId || stringValue(rawObject?.hubUid) || stringValue(rawObject?.hub_uid) || uniqueBase,
        name,
        manufacturer: geniushubProfile.manufacturer,
        model: geniushubProfile.model,
        host: configArg.host,
        protocol: configArg.host ? 'http' : 'local',
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const isSnapshotRecord = (valueArg: unknown): valueArg is ISimpleLocalSnapshot => Boolean(valueArg && typeof valueArg === 'object' && 'device' in valueArg && 'entities' in valueArg);

const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg));

const recordValue = (valueArg: unknown): Record<string, unknown> | undefined => isRecord(valueArg) ? valueArg : undefined;

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

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const idValue = (valueArg: unknown): string | undefined => typeof valueArg === 'number' && Number.isFinite(valueArg) ? String(valueArg) : stringValue(valueArg);

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) ? value : undefined;
};

const booleanValue = (valueArg: unknown): boolean | undefined => typeof valueArg === 'boolean' ? valueArg : undefined;

const booleanFromNumber = (valueArg: unknown): boolean | undefined => typeof valueArg === 'number' ? valueArg !== 0 : undefined;

const firstAssignedZone = (valueArg: Record<string, unknown>): string | undefined => {
  const assignedZones = arrayValue(valueArg.assignedZones);
  return assignedZones.length ? stringValue(assignedZones[0].name) : undefined;
};

const titleCase = (valueArg: string): string => valueArg.replace(/\b\w/g, (charArg) => charArg.toUpperCase());
