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 { IEnphaseEnvoyConfig } from './enphase_envoy.types.js';
import { enphaseEnvoyDefaultName, enphaseEnvoyDefaultPort, enphaseEnvoyProfile } from './enphase_envoy.types.js';

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

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

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

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

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

    const envoyData = recordValue(rawDataArg.envoy_data) || recordValue(rawDataArg.envoyData) || recordValue(rawDataArg.data) || rawDataArg;
    const hasEnvoyData = Boolean(envoyData.system_production || envoyData.systemProduction || envoyData.production || envoyData.inverters || envoyData.encharge_aggregate || envoyData.enpower || envoyData.dry_contact_status || envoyData.tariff);
    if (!hasEnvoyData) {
      return rawDataArg;
    }

    const host = configArg.host || stringValue(rawDataArg.host) || stringValue(envoyData.host);
    const port = configArg.port || numberValue(rawDataArg.port) || (host ? enphaseEnvoyDefaultPort : undefined);
    const serial = configArg.serial || configArg.uniqueId || stringValue(rawDataArg.serialNumber) || stringValue(rawDataArg.serial_number) || stringValue(envoyData.serialNumber) || stringValue(envoyData.serial_number);
    const name = configArg.name || stringValue(rawDataArg.name) || stringValue(envoyData.name) || (serial ? `${enphaseEnvoyDefaultName} ${serial}` : enphaseEnvoyDefaultName);
    const entities = this.entitiesFromEnvoyData(envoyData, serial || host || name);

    return {
      device: {
        id: configArg.uniqueId || serial || (host ? `${host}:${port || ''}` : undefined) || name,
        name,
        manufacturer: enphaseEnvoyProfile.manufacturer,
        model: stringValue(rawDataArg.envoyModel) || stringValue(rawDataArg.envoy_model) || stringValue(envoyData.envoyModel) || stringValue(envoyData.envoy_model) || enphaseEnvoyProfile.model,
        serialNumber: serial,
        host,
        port,
        protocol: configArg.useTls ? 'https' : enphaseEnvoyProfile.defaultProtocol,
        attributes: {
          firmware: stringValue(rawDataArg.firmware) || stringValue(envoyData.firmware),
          partNumber: stringValue(rawDataArg.partNumber) || stringValue(rawDataArg.part_number) || stringValue(envoyData.partNumber) || stringValue(envoyData.part_number),
          disableKeepAlive: configArg.disableKeepAlive ?? configArg.disable_keep_alive,
        },
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }

  private static entitiesFromEnvoyData(envoyDataArg: Record<string, unknown>, uniqueBaseArg: string): ISimpleLocalEntitySnapshot[] {
    const entities: ISimpleLocalEntitySnapshot[] = [];
    const production = recordValue(envoyDataArg.system_production) || recordValue(envoyDataArg.systemProduction) || recordValue(envoyDataArg.production);
    const consumption = recordValue(envoyDataArg.system_consumption) || recordValue(envoyDataArg.systemConsumption) || recordValue(envoyDataArg.consumption);
    const netConsumption = recordValue(envoyDataArg.system_net_consumption) || recordValue(envoyDataArg.systemNetConsumption) || recordValue(envoyDataArg.net_consumption);
    const enchargeAggregate = recordValue(envoyDataArg.encharge_aggregate) || recordValue(envoyDataArg.enchargeAggregate) || recordValue(envoyDataArg.battery_aggregate) || recordValue(envoyDataArg.batteryAggregate);
    const enpower = recordValue(envoyDataArg.enpower);
    const tariff = recordValue(envoyDataArg.tariff);
    const storageSettings = recordValue(tariff?.storage_settings) || recordValue(tariff?.storageSettings) || recordValue(envoyDataArg.storage_settings) || recordValue(envoyDataArg.storageSettings);

    this.addSystemPowerEntities(entities, uniqueBaseArg, production, 'production', 'Production');
    this.addSystemPowerEntities(entities, uniqueBaseArg, consumption, 'consumption', 'Consumption');
    this.addSystemPowerEntities(entities, uniqueBaseArg, netConsumption, 'net_consumption', 'Net Consumption');

    for (const inverter of recordsFrom(envoyDataArg.inverters)) {
      const serial = stringValue(inverter.serialNumber) || stringValue(inverter.serial_number) || stringValue(inverter.id);
      const watts = numberValue(inverter.last_report_watts ?? inverter.lastReportWatts ?? inverter.watts);
      if (serial && watts !== undefined) {
        entities.push(sensorEntity(uniqueBaseArg, `inverter_${serial}_power`, `Inverter ${serial} Power`, watts, 'W', 'power', 'measurement', { serialNumber: serial }));
      }
    }

    const batteryLevel = numberValue(enchargeAggregate?.state_of_charge ?? enchargeAggregate?.stateOfCharge ?? enchargeAggregate?.soc);
    if (batteryLevel !== undefined) {
      entities.push(sensorEntity(uniqueBaseArg, 'battery_level', 'Battery Level', batteryLevel, '%', 'battery', 'measurement'));
    }
    const reserveSoc = numberValue(enchargeAggregate?.reserve_state_of_charge ?? enchargeAggregate?.reserveStateOfCharge ?? storageSettings?.reserved_soc ?? storageSettings?.reservedSoc);
    if (reserveSoc !== undefined) {
      entities.push({
        ...sensorEntity(uniqueBaseArg, 'reserve_soc', 'Reserve Battery Level', reserveSoc, '%', 'battery', 'measurement'),
        platform: 'number',
        writable: true,
      });
    }

    const gridClosed = booleanFromClosed(enpower?.mains_oper_state ?? enpower?.mainsOperState ?? enpower?.grid_status ?? enpower?.gridStatus);
    if (gridClosed !== undefined) {
      entities.push({
        id: 'grid_status',
        uniqueId: `${enphaseEnvoyProfile.domain}_${SimpleLocalMapper.slug(uniqueBaseArg)}_grid_status`,
        name: 'Grid Status',
        platform: 'binary_sensor',
        state: gridClosed,
        available: true,
        writable: false,
        deviceClass: 'connectivity',
        attributes: {
          serialNumber: stringValue(enpower?.serial_number) || stringValue(enpower?.serialNumber),
          rawState: enpower?.mains_oper_state ?? enpower?.mainsOperState,
        },
      });
    }

    const gridEnabled = booleanFromClosed(enpower?.mains_admin_state ?? enpower?.mainsAdminState);
    if (gridEnabled !== undefined) {
      entities.push({
        id: 'grid_enabled',
        uniqueId: `${enphaseEnvoyProfile.domain}_${SimpleLocalMapper.slug(uniqueBaseArg)}_grid_enabled`,
        name: 'Grid Enabled',
        platform: 'switch',
        state: gridEnabled,
        available: true,
        writable: true,
        attributes: {
          serialNumber: stringValue(enpower?.serial_number) || stringValue(enpower?.serialNumber),
          serviceDomain: 'switch',
        },
      });
    }

    for (const relay of recordsFrom(envoyDataArg.dry_contact_status ?? envoyDataArg.dryContactStatus)) {
      const relayId = stringValue(relay.id) || stringValue(relay.relay_id) || stringValue(relay.relayId);
      if (!relayId) {
        continue;
      }
      entities.push({
        id: `relay_${SimpleLocalMapper.slug(relayId)}`,
        uniqueId: `${enphaseEnvoyProfile.domain}_${SimpleLocalMapper.slug(uniqueBaseArg)}_relay_${SimpleLocalMapper.slug(relayId)}`,
        name: stringValue(relay.name) || `Relay ${relayId}`,
        platform: 'switch',
        state: booleanFromClosed(relay.status ?? relay.state),
        available: true,
        writable: true,
        attributes: {
          relayId,
        },
      });
    }

    const chargeFromGrid = booleanValue(storageSettings?.charge_from_grid ?? storageSettings?.chargeFromGrid);
    if (chargeFromGrid !== undefined) {
      entities.push({
        id: 'charge_from_grid',
        uniqueId: `${enphaseEnvoyProfile.domain}_${SimpleLocalMapper.slug(uniqueBaseArg)}_charge_from_grid`,
        name: 'Charge From Grid',
        platform: 'switch',
        state: chargeFromGrid,
        available: true,
        writable: true,
      });
    }

    const storageMode = stringValue(storageSettings?.mode ?? storageSettings?.storage_mode ?? storageSettings?.storageMode);
    if (storageMode) {
      entities.push({
        id: 'storage_mode',
        uniqueId: `${enphaseEnvoyProfile.domain}_${SimpleLocalMapper.slug(uniqueBaseArg)}_storage_mode`,
        name: 'Storage Mode',
        platform: 'select',
        state: storageMode.toLowerCase(),
        available: true,
        writable: true,
        attributes: {
          options: ['backup', 'self_consumption', 'savings'],
        },
      });
    }

    return entities;
  }

  private static addSystemPowerEntities(entitiesArg: ISimpleLocalEntitySnapshot[], uniqueBaseArg: string, dataArg: Record<string, unknown> | undefined, keyArg: string, labelArg: string): void {
    if (!dataArg) {
      return;
    }
    const wattsNow = numberValue(dataArg.watts_now ?? dataArg.wattsNow ?? dataArg.current_power ?? dataArg.currentPower);
    if (wattsNow !== undefined) {
      entitiesArg.push(sensorEntity(uniqueBaseArg, keyArg, `Current Power ${labelArg}`, wattsNow, 'W', 'power', 'measurement'));
    }
    const today = numberValue(dataArg.watt_hours_today ?? dataArg.wattHoursToday ?? dataArg.energy_today ?? dataArg.energyToday);
    if (today !== undefined) {
      entitiesArg.push(sensorEntity(uniqueBaseArg, `daily_${keyArg}`, `${labelArg} Today`, today, 'Wh', 'energy', 'total_increasing'));
    }
    const lifetime = numberValue(dataArg.watt_hours_lifetime ?? dataArg.wattHoursLifetime ?? dataArg.lifetime_energy ?? dataArg.lifetimeEnergy);
    if (lifetime !== undefined) {
      entitiesArg.push(sensorEntity(uniqueBaseArg, `lifetime_${keyArg}`, `Lifetime ${labelArg}`, lifetime, 'Wh', 'energy', 'total_increasing'));
    }
  }
}

const sensorEntity = (uniqueBaseArg: string, idArg: string, nameArg: string, stateArg: unknown, unitArg?: string, deviceClassArg?: string, stateClassArg?: string, attributesArg?: Record<string, unknown>): ISimpleLocalEntitySnapshot => ({
  id: SimpleLocalMapper.slug(idArg),
  uniqueId: `${enphaseEnvoyProfile.domain}_${SimpleLocalMapper.slug(uniqueBaseArg)}_${SimpleLocalMapper.slug(idArg)}`,
  name: nameArg,
  platform: 'sensor',
  state: stateArg,
  available: true,
  writable: false,
  unit: unitArg,
  deviceClass: deviceClassArg,
  stateClass: stateClassArg,
  attributes: attributesArg,
});

const recordsFrom = (valueArg: unknown): Array<Record<string, unknown>> => {
  if (Array.isArray(valueArg)) {
    return valueArg.filter(isRecord);
  }
  if (isRecord(valueArg)) {
    return Object.entries(valueArg).flatMap(([keyArg, entryArg]) => isRecord(entryArg) ? [{ id: keyArg, ...entryArg }] : []);
  }
  return [];
};

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;

const numberValue = (valueArg: unknown): number | undefined => {
  const value = typeof valueArg === 'number' ? valueArg : typeof valueArg === 'string' ? Number(valueArg) : undefined;
  return value !== undefined && Number.isFinite(value) ? value : undefined;
};

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', 'yes', 'on', 'enabled', 'closed'].includes(value)) {
    return true;
  }
  if (['0', 'false', 'no', 'off', 'disabled', 'open'].includes(value)) {
    return false;
  }
  return undefined;
};

const booleanFromClosed = (valueArg: unknown): boolean | undefined => {
  const explicit = booleanValue(valueArg);
  if (explicit !== undefined) {
    return explicit;
  }
  const value = stringValue(valueArg)?.toLowerCase();
  if (!value) {
    return undefined;
  }
  if (['closed', 'close', 'on_grid', 'connected'].includes(value)) {
    return true;
  }
  if (['open', 'opened', 'off_grid', 'disconnected'].includes(value)) {
    return false;
  }
  return undefined;
};
