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 { IPeblarConfig, IPeblarRawApiData, TPeblarRawData } from './peblar.types.js';
import { peblarDefaultName, peblarDefaultPort, peblarProfile } from './peblar.types.js';

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

  public static toSnapshotFromRaw(configArg: IPeblarConfig, rawDataArg: TSimpleLocalRawData | TPeblarRawData): ISimpleLocalSnapshot {
    return this.toSnapshot({ config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
  }

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

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

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

  public static normalizeRawData(rawDataArg: unknown, configArg: IPeblarConfig): unknown {
    const parsedRawData = parseMaybeJson(rawDataArg);
    const rawObject = objectValue(parsedRawData);
    if (!rawObject || rawObject.device || Array.isArray(rawObject.entities)) {
      return parsedRawData;
    }

    const systemInformation = objectValue(rawObject.systemInformation);
    const userConfiguration = objectValue(rawObject.userConfiguration);
    const currentVersions = objectValue(rawObject.currentVersions);
    const availableVersions = objectValue(rawObject.availableVersions);
    const ev = objectValue(rawObject.ev);
    const meter = objectValue(rawObject.meter);
    const system = objectValue(rawObject.system);
    if (!systemInformation && !userConfiguration && !ev && !meter && !system) {
      return parsedRawData;
    }

    const endpoint = endpointFromConfig(configArg);
    const serialNumber = stringValue(readField(systemInformation, 'ProductSn', 'product_serial_number')) || stringValue(readField(system, 'ProductSn', 'product_serial_number'));
    const model = stringValue(readField(systemInformation, 'ProductModelName', 'product_model_name')) || stringValue(readField(system, 'ProductPn', 'product_part_number')) || 'Peblar EV Charger';
    const manufacturer = stringValue(readField(systemInformation, 'ProductVendorName', 'product_vendor_name')) || 'Peblar';
    const firmwareVersion = stringValue(readField(systemInformation, 'FwIdent', 'firmware_version')) || stringValue(readField(system, 'FirmwareVersion', 'firmware_version'));
    const hostname = stringValue(readField(systemInformation, 'Hostname', 'hostname'));
    const entities: ISimpleLocalEntitySnapshot[] = [];

    const activeErrors = arrayValue(readField(system, 'ActiveErrorCodes', 'active_error_codes')) || [];
    const activeWarnings = arrayValue(readField(system, 'ActiveWarningCodes', 'active_warning_codes')) || [];
    entities.push(binaryEntity('active_error_codes', 'Active Error Codes', activeErrors.length > 0, { codes: activeErrors, entityCategory: 'diagnostic' }));
    entities.push(binaryEntity('active_warning_codes', 'Active Warning Codes', activeWarnings.length > 0, { codes: activeWarnings, entityCategory: 'diagnostic' }));

    addSensor(entities, 'cp_state', 'CP State', cpStateValue(readField(ev, 'CpState', 'cp_state')), undefined, { deviceClass: 'enum' });
    addSensor(entities, 'charge_current_limit_source', 'Charge Current Limit Source', chargeLimiterValue(readField(ev, 'ChargeCurrentLimitSource', 'charge_current_limit_source')), undefined, { deviceClass: 'enum', entityCategory: 'diagnostic' });
    addSensor(entities, 'current_total', 'Current Total', totalCurrent(meter), 'mA', { deviceClass: 'current', entityCategory: 'diagnostic' });
    addSensor(entities, 'current_phase_1', 'Current Phase 1', readField(meter, 'CurrentPhase1', 'current_phase_1'), 'mA', { deviceClass: 'current', entityCategory: 'diagnostic' });
    addSensor(entities, 'current_phase_2', 'Current Phase 2', readField(meter, 'CurrentPhase2', 'current_phase_2'), 'mA', { deviceClass: 'current', entityCategory: 'diagnostic' });
    addSensor(entities, 'current_phase_3', 'Current Phase 3', readField(meter, 'CurrentPhase3', 'current_phase_3'), 'mA', { deviceClass: 'current', entityCategory: 'diagnostic' });
    addSensor(entities, 'energy_session', 'Energy Session', readField(meter, 'EnergySession', 'energy_session'), 'Wh', { deviceClass: 'energy', stateClass: 'total_increasing' });
    addSensor(entities, 'energy_total', 'Energy Total', readField(meter, 'EnergyTotal', 'energy_total'), 'Wh', { deviceClass: 'energy', stateClass: 'total_increasing', entityCategory: 'diagnostic' });
    addSensor(entities, 'power_total', 'Power Total', readField(meter, 'PowerTotal', 'power_total'), 'W', { deviceClass: 'power' });
    addSensor(entities, 'power_phase_1', 'Power Phase 1', readField(meter, 'PowerPhase1', 'power_phase_1'), 'W', { deviceClass: 'power', entityCategory: 'diagnostic' });
    addSensor(entities, 'power_phase_2', 'Power Phase 2', readField(meter, 'PowerPhase2', 'power_phase_2'), 'W', { deviceClass: 'power', entityCategory: 'diagnostic' });
    addSensor(entities, 'power_phase_3', 'Power Phase 3', readField(meter, 'PowerPhase3', 'power_phase_3'), 'W', { deviceClass: 'power', entityCategory: 'diagnostic' });
    addSensor(entities, 'voltage_phase_1', 'Voltage Phase 1', readField(meter, 'VoltagePhase1', 'voltage_phase_1'), 'V', { deviceClass: 'voltage', entityCategory: 'diagnostic' });
    addSensor(entities, 'voltage_phase_2', 'Voltage Phase 2', readField(meter, 'VoltagePhase2', 'voltage_phase_2'), 'V', { deviceClass: 'voltage', entityCategory: 'diagnostic' });
    addSensor(entities, 'voltage_phase_3', 'Voltage Phase 3', readField(meter, 'VoltagePhase3', 'voltage_phase_3'), 'V', { deviceClass: 'voltage', entityCategory: 'diagnostic' });
    addSensor(entities, 'uptime', 'Uptime', readField(system, 'Uptime', 'uptime'), 's', { deviceClass: 'duration', entityCategory: 'diagnostic' });

    const chargeCurrentLimit = numberValue(readField(ev, 'ChargeCurrentLimit', 'charge_current_limit'));
    if (chargeCurrentLimit !== undefined) {
      entities.push({
        id: 'charge_current_limit',
        name: 'Charge Current Limit',
        platform: 'number',
        state: Math.round(chargeCurrentLimit / 1000),
        writable: true,
        unit: 'A',
        deviceClass: 'current',
        attributes: { nativeValueMilliampere: chargeCurrentLimit, entityCategory: 'config' },
      });
      entities.push({
        id: 'charge',
        name: 'Charge',
        platform: 'switch',
        state: chargeCurrentLimit >= 6000,
        writable: true,
        attributes: { entityCategory: 'config' },
      });
    }

    const forceSinglePhase = booleanValue(readField(ev, 'Force1Phase', 'force_single_phase'));
    if (forceSinglePhase !== undefined) {
      entities.push({
        id: 'force_single_phase',
        name: 'Force Single Phase',
        platform: 'switch',
        state: forceSinglePhase,
        writable: true,
        attributes: { entityCategory: 'config' },
      });
    }

    const smartCharging = smartChargingValue(userConfiguration);
    if (smartCharging) {
      entities.push({
        id: 'smart_charging',
        name: 'Smart Charging',
        platform: 'select',
        state: smartCharging,
        writable: true,
        attributes: { entityCategory: 'config', options: ['default', 'fast_solar', 'pure_solar', 'scheduled', 'smart_solar'] },
      });
    }

    entities.push({ id: 'identify', name: 'Identify', platform: 'button', state: false, writable: true, attributes: { entityCategory: 'config' } });
    entities.push({ id: 'reboot', name: 'Reboot', platform: 'button', state: false, writable: true, attributes: { entityCategory: 'config' } });

    addUpdate(entities, 'firmware', 'Firmware', readField(currentVersions, 'Firmware', 'firmware'), readField(availableVersions, 'Firmware', 'firmware'));
    addUpdate(entities, 'customization', 'Customization', readField(currentVersions, 'Customization', 'customization'), readField(availableVersions, 'Customization', 'customization'));

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || serialNumber || endpoint?.host || peblarDefaultName,
        name: configArg.name || hostname || peblarDefaultName,
        manufacturer,
        model,
        serialNumber,
        host: endpoint?.host,
        port: endpoint?.port || (endpoint?.host ? peblarDefaultPort : undefined),
        protocol: 'http',
        configurationUrl: endpoint?.baseUrl,
        attributes: cleanAttributes({ firmwareVersion, ethernetMacAddress: readField(systemInformation, 'EthMacAddr', 'ethernet_mac_address'), wlanMacAddress: readField(systemInformation, 'WlanStaMacAddr', 'wlan_mac_address') }),
      },
      entities,
      online: true,
      updatedAt: stringValue(rawObject.fetchedAt) || new Date().toISOString(),
      source: 'http',
      rawData: parsedRawData,
    } satisfies ISimpleLocalSnapshot;
  }
}

const binaryEntity = (idArg: string, nameArg: string, stateArg: boolean, attributesArg: Record<string, unknown>): ISimpleLocalEntitySnapshot => ({
  id: idArg,
  name: nameArg,
  platform: 'binary_sensor',
  state: stateArg,
  attributes: cleanAttributes({ key: idArg, ...attributesArg }),
});

const addSensor = (entitiesArg: ISimpleLocalEntitySnapshot[], idArg: string, nameArg: string, valueArg: unknown, unitArg?: string, attributesArg: Record<string, unknown> = {}): void => {
  if (valueArg === undefined || valueArg === null || valueArg === '') {
    return;
  }
  entitiesArg.push({
    id: idArg,
    name: nameArg,
    platform: 'sensor',
    state: valueArg,
    unit: unitArg,
    stateClass: typeof valueArg === 'number' ? 'measurement' : undefined,
    attributes: cleanAttributes({ key: idArg, ...attributesArg }),
  });
};

const addUpdate = (entitiesArg: ISimpleLocalEntitySnapshot[], idArg: string, nameArg: string, installedArg: unknown, latestArg: unknown): void => {
  const installedVersion = stringValue(installedArg);
  const latestVersion = stringValue(latestArg);
  if (!installedVersion && !latestVersion) {
    return;
  }
  entitiesArg.push({
    id: idArg,
    name: nameArg,
    platform: 'update',
    state: Boolean(installedVersion && latestVersion && installedVersion !== latestVersion),
    attributes: cleanAttributes({ installedVersion, latestVersion }),
  });
};

const parseMaybeJson = (valueArg: unknown): unknown => {
  if (typeof valueArg !== 'string') {
    return valueArg;
  }
  try {
    return JSON.parse(valueArg) as IPeblarRawApiData;
  } catch {
    return valueArg;
  }
};

const objectValue = (valueArg: unknown): Record<string, unknown> | undefined => {
  return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : undefined;
};

const arrayValue = (valueArg: unknown): unknown[] | undefined => {
  return Array.isArray(valueArg) ? valueArg : undefined;
};

const readField = (objectArg: Record<string, unknown> | undefined, ...keysArg: string[]): unknown => {
  if (!objectArg) {
    return undefined;
  }
  for (const key of keysArg) {
    if (objectArg[key] !== undefined) {
      return objectArg[key];
    }
  }
  return undefined;
};

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

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

const 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;
};

const totalCurrent = (meterArg: Record<string, unknown> | undefined): number | undefined => {
  const phase1 = numberValue(readField(meterArg, 'CurrentPhase1', 'current_phase_1'));
  const phase2 = numberValue(readField(meterArg, 'CurrentPhase2', 'current_phase_2'));
  const phase3 = numberValue(readField(meterArg, 'CurrentPhase3', 'current_phase_3'));
  if (phase1 === undefined && phase2 === undefined && phase3 === undefined) {
    return undefined;
  }
  return (phase1 || 0) + (phase2 || 0) + (phase3 || 0);
};

const cpStateMap: Record<string, string> = {
  'State A': 'no_ev_connected',
  'State B': 'suspended',
  'State C': 'charging',
  'State D': 'charging',
  'State E': 'error',
  'State F': 'fault',
  Invalid: 'invalid',
};

const chargeLimiterMap: Record<string, string> = {
  'Charging cable': 'charging_cable',
  'Current limiter': 'current_limiter',
  'Dynamic load balancing': 'dynamic_load_balancing',
  'External power limit': 'external_power_limit',
  'Group load balancing': 'group_load_balancing',
  'Hardware limitation': 'hardware_limitation',
  'High temperature': 'high_temperature',
  'Household power limit': 'household_power_limit',
  'Installation limit': 'installation_limit',
  'Local Modbus API': 'local_modbus_api',
  'Local REST API': 'local_rest_api',
  'Local scheduled charging': 'local_scheduled_charging',
  'OCPP smart charging': 'ocpp_smart_charging',
  'Overcurrent protection': 'overcurrent_protection',
  'Phase imbalance': 'phase_imbalance',
  'Power factor': 'power_factor',
  'Solar charging': 'solar_charging',
};

const cpStateValue = (valueArg: unknown): string | undefined => {
  const value = stringValue(valueArg);
  return value ? cpStateMap[value] || SimpleLocalMapper.slug(value) : undefined;
};

const chargeLimiterValue = (valueArg: unknown): string | undefined => {
  const value = stringValue(valueArg);
  return value ? chargeLimiterMap[value] || SimpleLocalMapper.slug(value) : undefined;
};

const smartChargingValue = (configArg: Record<string, unknown> | undefined): string | undefined => {
  const explicit = stringValue(readField(configArg, 'smart_charging', 'SmartCharging'));
  if (explicit) {
    return explicit;
  }
  const scheduled = booleanValue(readField(configArg, 'ScheduledChargingEnable', 'scheduled_charging_enabled'));
  const solar = booleanValue(readField(configArg, 'SolarChargingEnable', 'solar_charging_enabled'));
  const mode = stringValue(readField(configArg, 'SolarChargingMode', 'solar_charging_mode'));
  if (scheduled === false && solar === false) {
    return 'default';
  }
  if (scheduled === true && solar === false) {
    return 'scheduled';
  }
  if (scheduled === false && solar === true) {
    if (mode === 'MaxSolar') {
      return 'fast_solar';
    }
    if (mode === 'PureSolar') {
      return 'pure_solar';
    }
    return 'smart_solar';
  }
  return undefined;
};

const endpointFromConfig = (configArg: IPeblarConfig): { baseUrl: string; host: string; port?: number } | undefined => {
  const url = stringValue(configArg.url) || stringValue(configArg.host);
  if (!url) {
    return undefined;
  }
  try {
    const parsed = new URL(url.includes('://') ? url : `http://${url}`);
    if (configArg.port) {
      parsed.port = String(configArg.port);
    }
    parsed.pathname = '';
    parsed.search = '';
    parsed.hash = '';
    return { baseUrl: parsed.toString().replace(/\/$/, ''), host: parsed.hostname, port: parsed.port ? Number(parsed.port) : undefined };
  } catch {
    return undefined;
  }
};

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