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 { INrgkickApiData, INrgkickConfig } from './nrgkick.types.js';
import { nrgkickProfile } from './nrgkick.types.js';

const chargingStatusMap: Record<number, string> = {
  1: 'standby',
  2: 'connected',
  3: 'charging',
  6: 'error',
  7: 'wakeup',
};

export class NrgkickMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<INrgkickConfig>, 'profile'>): ISimpleLocalSnapshot {
    const rawData = optionsArg.rawData ?? optionsArg.config.rawData;
    const apiData = this.apiData(rawData);
    if (apiData) {
      return this.snapshotFromApiData(optionsArg.config, apiData, optionsArg.online ?? true, optionsArg.source || 'http', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: nrgkickProfile });
  }

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

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

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

  private static snapshotFromApiData(configArg: INrgkickConfig, rawDataArg: INrgkickApiData, onlineArg: boolean, sourceArg: ISimpleLocalSnapshot['source'], errorArg?: string): ISimpleLocalSnapshot {
    const info = this.record(rawDataArg.info);
    const control = this.record(rawDataArg.control);
    const values = this.record(rawDataArg.values);
    const generalInfo = this.record(info.general);
    const networkInfo = this.record(info.network);
    const versions = this.record(info.versions);
    const valueGeneral = this.record(values.general);
    const energy = this.record(values.energy);
    const powerflow = this.record(values.powerflow);
    const connector = this.record(info.connector);
    const serial = this.stringValue(generalInfo.serial_number) || configArg.uniqueId || configArg.deviceId || configArg.macAddress;
    const host = this.endpointHost(configArg);
    const port = this.endpointPort(configArg);
    const deviceName = configArg.name || this.stringValue(generalInfo.device_name) || nrgkickProfile.defaultName || nrgkickProfile.displayName;
    const entities: ISimpleLocalEntitySnapshot[] = [];

    this.pushEntity(entities, 'charge_permitted', 'Charge Permitted', this.booleanValue(valueGeneral.charge_permitted), 'binary_sensor');
    this.pushEntity(entities, 'charging_enabled', 'Charging Enabled', !this.booleanValue(control.charge_pause), 'switch', undefined, { controlKey: 'charge_pause' });
    this.pushEntity(entities, 'status', 'Status', this.statusValue(valueGeneral.status), 'sensor', undefined, { rawStatus: valueGeneral.status });
    this.pushEntity(entities, 'charging_current', 'Charging Current', this.numberValue(powerflow.charging_current), 'sensor', 'A', { deviceClass: 'current', stateClass: 'measurement' });
    this.pushEntity(entities, 'total_active_power', 'Total Active Power', this.numberValue(powerflow.total_active_power), 'sensor', 'W', { deviceClass: 'power', stateClass: 'measurement' });
    this.pushEntity(entities, 'charged_energy', 'Charged Energy', this.numberValue(energy.charged_energy), 'sensor', 'Wh', { deviceClass: 'energy', stateClass: 'total_increasing' });
    this.pushEntity(entities, 'total_charged_energy', 'Total Charged Energy', this.numberValue(energy.total_charged_energy), 'sensor', 'Wh', { deviceClass: 'energy', stateClass: 'total_increasing' });
    this.pushEntity(entities, 'current_set', 'Charging Current Setpoint', this.numberValue(control.current_set), 'number', 'A', { controlKey: 'current_set', min: 6, max: this.numberValue(generalInfo.rated_current) || this.numberValue(connector.max_current) || 32 });
    this.pushEntity(entities, 'energy_limit', 'Energy Limit', this.numberValue(control.energy_limit), 'number', 'Wh', { controlKey: 'energy_limit', min: 0 });
    this.pushEntity(entities, 'phase_count', 'Phase Count', this.numberValue(control.phase_count), 'number', undefined, { controlKey: 'phase_count', min: 1, max: 3 });
    this.pushEntity(entities, 'network_rssi', 'Network RSSI', this.numberValue(networkInfo.rssi), 'sensor', 'dBm', { deviceClass: 'signal_strength', stateClass: 'measurement' });

    return {
      device: {
        id: configArg.uniqueId || serial || (host ? `${host}:${port || ''}` : undefined) || deviceName,
        name: deviceName,
        manufacturer: 'DiniTech',
        model: this.stringValue(generalInfo.model_type) || nrgkickProfile.model,
        serialNumber: serial,
        host,
        port,
        protocol: configArg.ssl || configArg.useTls ? 'https' : 'http',
        configurationUrl: host ? `${configArg.ssl || configArg.useTls ? 'https' : 'http'}://${host}${port ? `:${port}` : ''}` : undefined,
        attributes: this.cleanAttributes({
          macAddress: this.stringValue(networkInfo.mac_address) || configArg.macAddress,
          swVersion: this.stringValue(versions.sw_sm),
          hwVersion: this.stringValue(versions.hw_sm),
          ssid: this.stringValue(networkInfo.ssid),
          ratedCurrent: this.numberValue(generalInfo.rated_current),
          connectorMaxCurrent: this.numberValue(connector.max_current),
        }),
      },
      entities: entities.length ? entities : [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: onlineArg, available: true }],
      online: onlineArg,
      updatedAt: this.stringValue(rawDataArg.fetchedAt) || new Date().toISOString(),
      source: sourceArg,
      rawData: rawDataArg,
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: Boolean(configArg.host || configArg.url),
        liveControlRequiresExecutor: false,
      },
    };
  }

  private static apiData(valueArg: unknown): INrgkickApiData | undefined {
    const value = this.record(valueArg);
    return value && ('info' in value || 'control' in value || 'values' in value) ? value as INrgkickApiData : undefined;
  }

  private static pushEntity(entitiesArg: ISimpleLocalEntitySnapshot[], idArg: string, nameArg: string, valueArg: unknown, platformArg: ISimpleLocalEntitySnapshot['platform'], unitArg?: string, attributesArg?: Record<string, unknown>): void {
    if (valueArg === undefined) {
      return;
    }
    entitiesArg.push({
      id: idArg,
      name: nameArg,
      platform: platformArg,
      state: valueArg,
      available: true,
      writable: platformArg === 'switch' || platformArg === 'number',
      unit: unitArg,
      attributes: this.cleanAttributes(attributesArg),
    });
  }

  private static statusValue(valueArg: unknown): string | number | undefined {
    const value = this.numberValue(valueArg);
    if (value === undefined) {
      return typeof valueArg === 'string' ? valueArg : undefined;
    }
    return chargingStatusMap[value] || value;
  }

  private static endpointHost(configArg: INrgkickConfig): string | undefined {
    if (configArg.host) {
      return this.urlHost(configArg.host) || configArg.host;
    }
    if (configArg.url) {
      return this.urlHost(configArg.url);
    }
    return undefined;
  }

  private static endpointPort(configArg: INrgkickConfig): number | undefined {
    if (configArg.port) {
      return configArg.port;
    }
    const source = configArg.url || configArg.host;
    if (!source) {
      return undefined;
    }
    try {
      const url = new URL(source.includes('://') ? source : `http://${source}`);
      return url.port ? Number(url.port) : undefined;
    } catch {
      return undefined;
    }
  }

  private static urlHost(valueArg: string): string | undefined {
    try {
      return new URL(valueArg.includes('://') ? valueArg : `http://${valueArg}`).hostname;
    } catch {
      return undefined;
    }
  }

  private static record(valueArg: unknown): Record<string, unknown> {
    return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : {};
  }

  private static stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg ? valueArg : typeof valueArg === 'number' && Number.isFinite(valueArg) ? String(valueArg) : undefined;
  }

  private static numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg === 'string' && valueArg.trim()) {
      const parsed = Number(valueArg);
      return Number.isFinite(parsed) ? parsed : undefined;
    }
    return undefined;
  }

  private static booleanValue(valueArg: unknown): boolean | undefined {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'number') {
      return valueArg !== 0;
    }
    if (typeof valueArg === 'string') {
      if (valueArg === '1' || valueArg.toLowerCase() === 'true') {
        return true;
      }
      if (valueArg === '0' || valueArg.toLowerCase() === 'false') {
        return false;
      }
    }
    return undefined;
  }

  private static cleanAttributes(attributesArg?: Record<string, unknown>): Record<string, unknown> | undefined {
    const entries = Object.entries(attributesArg || {}).filter(([, value]) => value !== undefined && value !== null && value !== '');
    return entries.length ? Object.fromEntries(entries) : undefined;
  }
}
