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 { IIskraConfig, IIskraRestRawData } from './iskra.types.js';
import { iskraDefaultHttpPort, iskraManufacturer, iskraProfile } from './iskra.types.js';

interface IParsedMeasurement {
  value: number;
  unit?: string;
}

interface IIskraSensorDefinition {
  key: string;
  name: string;
  unit?: string;
  deviceClass?: string;
  stateClass?: string;
  measurement(rawDeviceArg: Record<string, unknown>): IParsedMeasurement | undefined;
}

const sensorDefinitions: IIskraSensorDefinition[] = [
  sensor('total_active_power', 'Total active power', (deviceArg) => measurementValue(objectValue(deviceArg.measurements)?.Total || objectValue(deviceArg.measurements)?.total, ['P', 'active_power'], 'W'), { unit: 'W', deviceClass: 'power', stateClass: 'measurement' }),
  sensor('total_reactive_power', 'Total reactive power', (deviceArg) => measurementValue(objectValue(deviceArg.measurements)?.Total || objectValue(deviceArg.measurements)?.total, ['Q', 'reactive_power'], 'var'), { unit: 'var', deviceClass: 'reactive_power', stateClass: 'measurement' }),
  sensor('total_apparent_power', 'Total apparent power', (deviceArg) => measurementValue(objectValue(deviceArg.measurements)?.Total || objectValue(deviceArg.measurements)?.total, ['S', 'apparent_power'], 'VA'), { unit: 'VA', deviceClass: 'apparent_power', stateClass: 'measurement' }),
  sensor('phase1_power', 'Phase 1 power', (deviceArg) => phaseMeasurement(deviceArg, 0, ['P', 'active_power'], 'W'), { unit: 'W', deviceClass: 'power', stateClass: 'measurement' }),
  sensor('phase2_power', 'Phase 2 power', (deviceArg) => phaseMeasurement(deviceArg, 1, ['P', 'active_power'], 'W'), { unit: 'W', deviceClass: 'power', stateClass: 'measurement' }),
  sensor('phase3_power', 'Phase 3 power', (deviceArg) => phaseMeasurement(deviceArg, 2, ['P', 'active_power'], 'W'), { unit: 'W', deviceClass: 'power', stateClass: 'measurement' }),
  sensor('phase1_voltage', 'Phase 1 voltage', (deviceArg) => phaseMeasurement(deviceArg, 0, ['U', 'voltage'], 'V'), { unit: 'V', deviceClass: 'voltage', stateClass: 'measurement' }),
  sensor('phase2_voltage', 'Phase 2 voltage', (deviceArg) => phaseMeasurement(deviceArg, 1, ['U', 'voltage'], 'V'), { unit: 'V', deviceClass: 'voltage', stateClass: 'measurement' }),
  sensor('phase3_voltage', 'Phase 3 voltage', (deviceArg) => phaseMeasurement(deviceArg, 2, ['U', 'voltage'], 'V'), { unit: 'V', deviceClass: 'voltage', stateClass: 'measurement' }),
  sensor('phase1_current', 'Phase 1 current', (deviceArg) => phaseMeasurement(deviceArg, 0, ['I', 'current'], 'A'), { unit: 'A', deviceClass: 'current', stateClass: 'measurement' }),
  sensor('phase2_current', 'Phase 2 current', (deviceArg) => phaseMeasurement(deviceArg, 1, ['I', 'current'], 'A'), { unit: 'A', deviceClass: 'current', stateClass: 'measurement' }),
  sensor('phase3_current', 'Phase 3 current', (deviceArg) => phaseMeasurement(deviceArg, 2, ['I', 'current'], 'A'), { unit: 'A', deviceClass: 'current', stateClass: 'measurement' }),
  sensor('frequency', 'Frequency', (deviceArg) => measurementValue(objectValue(deviceArg.measurements), ['Frequency', 'frequency'], 'Hz'), { unit: 'Hz', deviceClass: 'frequency', stateClass: 'measurement' }),
];

export class IskraMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IIskraConfig>, 'profile'>): ISimpleLocalSnapshot {
    const restRawData = restRawDataValue(optionsArg.rawData);
    if (restRawData) {
      return this.toRestApiSnapshot(optionsArg, restRawData);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: iskraProfile });
  }

  public static toSnapshotFromRaw(configArg: IIskraConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({ profile: iskraProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
  }

  public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
    return SimpleLocalMapper.toDevices(iskraProfile, snapshotArg);
  }

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

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

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

  private static toRestApiSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IIskraConfig>, 'profile'>, rawDataArg: IIskraRestRawData): ISimpleLocalSnapshot {
    const gateway = objectValue(rawDataArg.gateway) || {};
    const host = hostValue(optionsArg.config);
    const port = optionsArg.config.port || (host ? iskraDefaultHttpPort : undefined);
    const protocol = optionsArg.config.useTls ? 'https' : 'http';
    const deviceName = optionsArg.config.name || stringValue(gateway.description) || stringValue(gateway.model) || iskraProfile.defaultName || iskraProfile.displayName;
    const snapshot: ISimpleLocalSnapshot = {
      device: {
        id: optionsArg.config.uniqueId || stringValue(gateway.serial) || (host ? `${host}:${port || ''}` : undefined) || deviceName,
        name: deviceName,
        manufacturer: iskraManufacturer,
        model: stringValue(gateway.model) || iskraProfile.model,
        serialNumber: stringValue(gateway.serial),
        host,
        port,
        protocol,
        configurationUrl: host ? `${protocol}://${host}${port ? `:${port}` : ''}` : undefined,
        attributes: cleanAttributes({
          protocol: 'rest_api',
          swVersion: stringValue(gateway.sw_ver),
          deviceCount: rawDataArg.devices.length,
          ...optionsArg.config.metadata,
        }),
      },
      entities: this.entitiesFromRestRawData(rawDataArg),
      online: optionsArg.online ?? optionsArg.config.online ?? rawDataArg.devices.length > 0,
      updatedAt: rawDataArg.fetchedAt || new Date().toISOString(),
      source: optionsArg.source || 'http',
      rawData: rawDataArg,
      error: optionsArg.error || rawDataArg.errors?.devices,
      capabilities: {
        localRead: true,
        localControl: false,
        restApi: true,
      },
    };

    return SimpleLocalMapper.toSnapshot({ profile: iskraProfile, config: { ...optionsArg.config, snapshot }, source: snapshot.source, online: snapshot.online, error: snapshot.error });
  }

  private static entitiesFromRestRawData(rawDataArg: IIskraRestRawData): ISimpleLocalEntitySnapshot[] {
    const entities: ISimpleLocalEntitySnapshot[] = [];
    for (const device of rawDataArg.devices) {
      const basicInfo = objectValue(device.basicInfo) || {};
      const deviceKey = this.slug(stringValue(basicInfo.serial) || stringValue(basicInfo.model) || `device_${device.index}`);
      const deviceName = stringValue(basicInfo.description) || stringValue(basicInfo.model) || `Device ${device.index + 1}`;
      const rawDevice = { ...device, measurements: device.measurements, counters: device.counters };

      for (const definition of sensorDefinitions) {
        const measurement = definition.measurement(rawDevice);
        if (!measurement) {
          continue;
        }
        entities.push({
          id: `${deviceKey}_${definition.key}`,
          uniqueId: `${iskraProfile.domain}_${deviceKey}_${definition.key}`,
          name: `${deviceName} ${definition.name}`,
          platform: 'sensor',
          state: measurement.value,
          available: true,
          unit: measurement.unit || definition.unit,
          deviceClass: definition.deviceClass,
          stateClass: definition.stateClass,
          attributes: cleanAttributes({
            key: definition.key,
            childIndex: device.index,
            childSerial: stringValue(basicInfo.serial),
            childModel: stringValue(basicInfo.model),
            nativeUnitOfMeasurement: measurement.unit || definition.unit,
          }),
        });
      }

      this.counterEntities(entities, deviceKey, deviceName, device, 'non_resettable', 'non_resettable_counter', 4);
      this.counterEntities(entities, deviceKey, deviceName, device, 'resettable', 'resettable_counter', 8);
    }
    return entities;
  }

  private static counterEntities(entitiesArg: ISimpleLocalEntitySnapshot[], deviceKeyArg: string, deviceNameArg: string, rawDeviceArg: { index: number; counters?: Record<string, unknown>; basicInfo: Record<string, unknown> }, sourceKeyArg: string, keyPrefixArg: string, limitArg: number): void {
    const counters = arrayValue(objectValue(rawDeviceArg.counters)?.[sourceKeyArg]).slice(0, limitArg);
    counters.forEach((counterArg, indexArg) => {
      const measurement = parseMeasurement(counterArg.value, stringValue(counterArg.unit));
      if (!measurement) {
        return;
      }
      const key = `${keyPrefixArg}_${indexArg + 1}`;
      const unit = measurement.unit || stringValue(counterArg.unit);
      entitiesArg.push({
        id: `${deviceKeyArg}_${key}`,
        uniqueId: `${iskraProfile.domain}_${deviceKeyArg}_${key}`,
        name: `${deviceNameArg} ${titleCase(key)}`,
        platform: 'sensor',
        state: measurement.value,
        available: true,
        unit,
        deviceClass: unit === 'Wh' ? 'energy' : undefined,
        stateClass: 'total_increasing',
        attributes: cleanAttributes({
          key,
          childIndex: rawDeviceArg.index,
          childSerial: stringValue(rawDeviceArg.basicInfo.serial),
          childModel: stringValue(rawDeviceArg.basicInfo.model),
          direction: stringValue(counterArg.direction),
          nativeUnitOfMeasurement: unit,
        }),
      });
    });
  }
}

function sensor(keyArg: string, nameArg: string, valueArg: IIskraSensorDefinition['measurement'], optionsArg: Omit<IIskraSensorDefinition, 'key' | 'name' | 'measurement'> = {}): IIskraSensorDefinition {
  return {
    key: keyArg,
    name: nameArg,
    measurement: valueArg,
    ...optionsArg,
  };
}

const restRawDataValue = (valueArg: unknown): IIskraRestRawData | undefined => {
  const rawData = objectValue(valueArg);
  return rawData && Array.isArray(rawData.devices) && typeof rawData.fetchedAt === 'string' ? rawData as IIskraRestRawData : undefined;
};

const phaseMeasurement = (deviceArg: Record<string, unknown>, phaseIndexArg: number, keysArg: string[], defaultUnitArg: string): IParsedMeasurement | undefined => {
  const measurements = objectValue(deviceArg.measurements);
  const phases = arrayValue(measurements?.Phases || measurements?.phases);
  return measurementValue(phases[phaseIndexArg], keysArg, defaultUnitArg);
};

const measurementValue = (sourceArg: unknown, keysArg: string[], defaultUnitArg: string): IParsedMeasurement | undefined => {
  const source = objectValue(sourceArg);
  if (!source) {
    return undefined;
  }
  for (const key of keysArg) {
    const parsed = parseMeasurement(source[key], defaultUnitArg);
    if (parsed) {
      return parsed;
    }
  }
  return undefined;
};

const parseMeasurement = (valueArg: unknown, defaultUnitArg?: string): IParsedMeasurement | undefined => {
  const object = objectValue(valueArg);
  if (object && object.value !== undefined) {
    return parseMeasurementValue(object.value, stringValue(object.unit) || defaultUnitArg);
  }
  if (typeof valueArg === 'string') {
    const match = valueArg.trim().match(/^(-?\d+(?:\.\d+)?)\s*(.*)$/);
    return match ? parseMeasurementValue(Number(match[1]), stringValue(match[2]) || defaultUnitArg) : undefined;
  }
  return parseMeasurementValue(valueArg, defaultUnitArg);
};

const parseMeasurementValue = (valueArg: unknown, unitArg?: string): IParsedMeasurement | undefined => {
  const value = typeof valueArg === 'number' ? valueArg : typeof valueArg === 'string' && valueArg.trim() ? Number(valueArg) : undefined;
  return value !== undefined && Number.isFinite(value) ? { value, unit: unitArg } : undefined;
};

const hostValue = (configArg: IIskraConfig): string | undefined => {
  const rawUrl = stringValue(configArg.url) || stringValue(configArg.baseUrl);
  if (rawUrl) {
    try {
      return new URL(rawUrl).hostname;
    } catch {
      return undefined;
    }
  }
  return stringValue(configArg.host);
};

const cleanAttributes = (valueArg: Record<string, unknown>): Record<string, unknown> => Object.fromEntries(Object.entries(valueArg).filter(([, value]) => value !== undefined && value !== ''));
const objectValue = (valueArg: unknown): Record<string, unknown> | undefined => valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : undefined;
const arrayValue = (valueArg: unknown): Array<Record<string, unknown>> => Array.isArray(valueArg) ? valueArg.filter((itemArg): itemArg is Record<string, unknown> => Boolean(objectValue(itemArg))) : [];
const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
const titleCase = (valueArg: string): string => valueArg.replace(/_/g, ' ').replace(/\b\w/g, (matchArg) => matchArg.toUpperCase());
