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 { IKostalPlenticoreConfig, IKostalPlenticoreProcessDataItem, IKostalPlenticoreRawData } from './kostal_plenticore.types.js';
import { kostalPlenticoreDefaultName, kostalPlenticoreDefaultPort, kostalPlenticoreProfile } from './kostal_plenticore.types.js';

interface IProcessDescription {
  name: string;
  unit?: string;
  deviceClass?: string;
  stateClass?: string;
  formatter?: 'round' | 'float' | 'energy' | 'inverter_state' | 'em_state';
}

export class KostalPlenticoreMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IKostalPlenticoreConfig>, 'profile'>): ISimpleLocalSnapshot {
    const rawData = optionsArg.rawData ?? optionsArg.config.rawData;
    if (isKostalRawData(rawData)) {
      return this.toSnapshotFromKostalRaw(optionsArg.config, rawData, optionsArg.online, optionsArg.source, optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: kostalPlenticoreProfile });
  }

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

  public static offlineSnapshot(configArg: IKostalPlenticoreConfig, errorArg: string): ISimpleLocalSnapshot {
    return this.toSnapshotFromKostalRaw(configArg, { processData: {}, source: 'runtime', fetchedAt: new Date().toISOString() }, false, 'runtime', errorArg);
  }

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

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

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

  public static commandEntity(snapshotArg: ISimpleLocalSnapshot, entityIdArg?: string): ISimpleLocalEntitySnapshot | undefined {
    if (!entityIdArg) {
      return undefined;
    }
    const entitySlug = entityIdArg.split('.').pop();
    return snapshotArg.entities.find((entityArg) => this.slug(`${snapshotArg.device.name}_${entityArg.id}`) === entitySlug || this.slug(entityArg.name) === entitySlug || entityArg.id === entitySlug);
  }

  private static toSnapshotFromKostalRaw(configArg: IKostalPlenticoreConfig, rawDataArg: IKostalPlenticoreRawData, onlineArg = true, sourceArg: ISimpleLocalSnapshot['source'] = 'manual', errorArg?: string): ISimpleLocalSnapshot {
    const settings = rawDataArg.settings || {};
    const localSettings = settings['devices:local'] || {};
    const networkSettings = settings['scb:network'] || {};
    const host = endpointHost(configArg.host || configArg.url);
    const port = configArg.port || endpointPort(configArg.host || configArg.url) || (host ? kostalPlenticoreDefaultPort : undefined);
    const source = rawDataArg.source || sourceArg;
    const productName = [localSettings['Branding:ProductName1'], localSettings['Branding:ProductName2']].filter(Boolean).join(' ').trim();
    const deviceName = configArg.name || networkSettings['Network:Hostname'] || networkSettings.Hostname || rawDataArg.version?.hostname || kostalPlenticoreDefaultName;
    const entities = [
      ...this.processEntities(rawDataArg.processData || {}, onlineArg),
      ...this.settingEntities(settings, onlineArg),
    ];

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || localSettings['Properties:SerialNo'] || (host ? `${host}:${port || ''}` : undefined) || deviceName,
        name: deviceName,
        manufacturer: 'Kostal',
        model: configArg.metadata?.model as string || productName || 'Plenticore Solar Inverter',
        serialNumber: localSettings['Properties:SerialNo'],
        host,
        port,
        protocol: configArg.useTls ? 'https' : 'http',
        configurationUrl: host ? `${configArg.useTls ? 'https' : 'http'}://${host}${port && port !== 80 ? `:${port}` : ''}` : undefined,
        attributes: cleanAttributes({
          apiVersion: rawDataArg.version?.api_version,
          apiName: rawDataArg.version?.name,
          softwareVersion: rawDataArg.version?.sw_version,
          iocVersion: localSettings['Properties:VersionIOC'],
          mcVersion: localSettings['Properties:VersionMC'],
          source,
        }),
      },
      entities: entities.length ? entities : [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: onlineArg, available: true }],
      online: onlineArg,
      updatedAt: rawDataArg.fetchedAt || new Date().toISOString(),
      source,
      rawData: rawDataArg,
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: Boolean(configArg.host || configArg.url || configArg.client || configArg.commandExecutor),
        liveControlRequiresAuth: true,
      },
    };
  }

  private static processEntities(processDataArg: IKostalPlenticoreRawData['processData'], onlineArg: boolean): ISimpleLocalEntitySnapshot[] {
    const entities: ISimpleLocalEntitySnapshot[] = [];
    for (const [moduleId, moduleData] of Object.entries(processDataArg || {})) {
      for (const item of processItems(moduleData)) {
        const description = processDescriptions[`${moduleId}/${item.id}`] || descriptionFromProcessItem(item);
        entities.push({
          id: this.slug(`${moduleId}_${item.id}`),
          name: description.name,
          platform: 'sensor',
          state: formatProcessValue(item.value, description.formatter),
          available: onlineArg,
          unit: description.unit || item.unit,
          deviceClass: description.deviceClass,
          stateClass: description.stateClass,
          attributes: cleanAttributes({ moduleId, dataId: item.id, rawValue: item.value, source: 'processdata' }),
        });
      }
    }
    return entities;
  }

  private static settingEntities(settingsArg: Record<string, Record<string, string>>, onlineArg: boolean): ISimpleLocalEntitySnapshot[] {
    const local = settingsArg['devices:local'] || {};
    const entities: ISimpleLocalEntitySnapshot[] = [];
    const switchDescriptions = [
      { id: 'battery_strategy', name: 'Battery Strategy', dataId: 'Battery:Strategy', onValue: '1', offValue: '2', onLabel: 'Automatic', offLabel: 'Automatic economical' },
      { id: 'battery_manual_charge', name: 'Battery Manual Charge', dataId: 'Battery:ManualCharge', onValue: '1', offValue: '0', onLabel: 'On', offLabel: 'Off' },
    ];
    for (const description of switchDescriptions) {
      if (local[description.dataId] !== undefined) {
        entities.push({
          id: description.id,
          name: description.name,
          platform: 'switch',
          state: local[description.dataId] === description.onValue,
          available: onlineArg,
          writable: true,
          attributes: cleanAttributes({ moduleId: 'devices:local', dataId: description.dataId, onValue: description.onValue, offValue: description.offValue, onLabel: description.onLabel, offLabel: description.offLabel, rawValue: local[description.dataId] }),
        });
      }
    }

    const numberDescriptions = [
      { id: 'battery_min_soc', name: 'Battery min SoC', dataId: 'Battery:MinSoc', unit: '%', deviceClass: 'battery' },
      { id: 'battery_min_home_consumption', name: 'Battery min Home Consumption', dataId: 'Battery:MinHomeComsumption', unit: 'W', deviceClass: 'power' },
      { id: 'active_power_limitation', name: 'Active Power Limitation', dataId: 'Inverter:ActivePowerLimitation', unit: 'W', deviceClass: 'power' },
    ];
    for (const description of numberDescriptions) {
      if (local[description.dataId] !== undefined) {
        entities.push({
          id: description.id,
          name: description.name,
          platform: 'number',
          state: roundValue(local[description.dataId]),
          available: onlineArg,
          writable: true,
          unit: description.unit,
          deviceClass: description.deviceClass,
          attributes: cleanAttributes({ moduleId: 'devices:local', dataId: description.dataId, rawValue: local[description.dataId] }),
        });
      }
    }

    const batteryModeOptions = ['None', 'Battery:SmartBatteryControl:Enable', 'Battery:TimeControl:Enable'];
    if (batteryModeOptions.slice(1).some((dataIdArg) => local[dataIdArg] !== undefined)) {
      const current = batteryModeOptions.slice(1).find((dataIdArg) => local[dataIdArg] === '1') || 'None';
      entities.push({
        id: 'battery_charge',
        name: 'Battery Charging / Usage mode',
        platform: 'select',
        state: current,
        available: onlineArg,
        writable: true,
        attributes: cleanAttributes({ moduleId: 'devices:local', options: batteryModeOptions, optionSettingIds: batteryModeOptions.slice(1) }),
      });
    }
    return entities;
  }
}

const processDescriptions: Record<string, IProcessDescription> = {
  'devices:local/Inverter:State': { name: 'Inverter State', formatter: 'inverter_state' },
  'devices:local/Dc_P': { name: 'Solar Power', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local/Grid_P': { name: 'Grid Power', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local/HomeBat_P': { name: 'Home Power from Battery', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local/HomeGrid_P': { name: 'Home Power from Grid', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local/HomeOwn_P': { name: 'Home Power from Own', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local/HomePv_P': { name: 'Home Power from PV', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local/Home_P': { name: 'Home Power', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local/PV2Bat_P': { name: 'PV to Battery Power', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local/EM_State': { name: 'Energy Manager State', formatter: 'em_state' },
  'devices:local:ac/P': { name: 'AC Power', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local:pv1/P': { name: 'DC1 Power', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local:pv1/U': { name: 'DC1 Voltage', unit: 'V', deviceClass: 'voltage', stateClass: 'measurement', formatter: 'round' },
  'devices:local:pv1/I': { name: 'DC1 Current', unit: 'A', deviceClass: 'current', stateClass: 'measurement', formatter: 'float' },
  'devices:local:pv2/P': { name: 'DC2 Power', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local:pv2/U': { name: 'DC2 Voltage', unit: 'V', deviceClass: 'voltage', stateClass: 'measurement', formatter: 'round' },
  'devices:local:pv2/I': { name: 'DC2 Current', unit: 'A', deviceClass: 'current', stateClass: 'measurement', formatter: 'float' },
  'devices:local:pv3/P': { name: 'DC3 Power', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local:pv3/U': { name: 'DC3 Voltage', unit: 'V', deviceClass: 'voltage', stateClass: 'measurement', formatter: 'round' },
  'devices:local:pv3/I': { name: 'DC3 Current', unit: 'A', deviceClass: 'current', stateClass: 'measurement', formatter: 'float' },
  'devices:local:battery/Cycles': { name: 'Battery Cycles', stateClass: 'total_increasing', formatter: 'round' },
  'devices:local:battery/P': { name: 'Battery Power', unit: 'W', deviceClass: 'power', stateClass: 'measurement', formatter: 'round' },
  'devices:local:battery/SoC': { name: 'Battery SoC', unit: '%', deviceClass: 'battery', stateClass: 'measurement', formatter: 'round' },
  'scb:event/Event:ActiveErrorCnt': { name: 'Active Alarms', formatter: 'round' },
};

const inverterStates: Record<number, string> = {
  0: 'Off',
  1: 'Init',
  2: 'IsoMEas',
  3: 'GridCheck',
  4: 'StartUp',
  6: 'FeedIn',
  7: 'Throttled',
  8: 'ExtSwitchOff',
  9: 'Update',
  10: 'Standby',
  11: 'GridSync',
  12: 'GridPreCheck',
  13: 'GridSwitchOff',
  14: 'Overheating',
  15: 'Shutdown',
  16: 'ImproperDcVoltage',
  17: 'ESB',
  18: 'DcCheck',
};

const energyManagerStates: Record<number, string> = {
  0: 'Idle',
  1: 'n/a',
  2: 'Emergency Battery Charge',
  4: 'n/a',
  8: 'Winter Mode Step 1',
  16: 'Winter Mode Step 2',
};

const isKostalRawData = (valueArg: unknown): valueArg is IKostalPlenticoreRawData => {
  return Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) && ('processData' in valueArg || 'settings' in valueArg || 'version' in valueArg));
};

const processItems = (moduleDataArg: IKostalPlenticoreProcessDataItem[] | Record<string, unknown>): IKostalPlenticoreProcessDataItem[] => {
  if (Array.isArray(moduleDataArg)) {
    return moduleDataArg;
  }
  return Object.entries(moduleDataArg).map(([idArg, valueArg]) => ({ id: idArg, value: primitiveValue(valueArg) }));
};

const primitiveValue = (valueArg: unknown): IKostalPlenticoreProcessDataItem['value'] => {
  if (valueArg === null || ['string', 'number', 'boolean'].includes(typeof valueArg)) {
    return valueArg as IKostalPlenticoreProcessDataItem['value'];
  }
  return String(valueArg ?? '');
};

const descriptionFromProcessItem = (itemArg: IKostalPlenticoreProcessDataItem): IProcessDescription => ({
  name: titleCase(itemArg.id),
  unit: itemArg.unit,
  deviceClass: deviceClassForUnit(itemArg.unit),
  stateClass: typeof itemArg.value === 'number' ? 'measurement' : undefined,
  formatter: itemArg.unit === 'Wh' ? 'energy' : typeof itemArg.value === 'number' ? 'float' : undefined,
});

const formatProcessValue = (valueArg: IKostalPlenticoreProcessDataItem['value'], formatterArg?: IProcessDescription['formatter']): unknown => {
  if (formatterArg === 'inverter_state') {
    return inverterStates[Number(valueArg)] || valueArg;
  }
  if (formatterArg === 'em_state') {
    return energyManagerStates[Number(valueArg)] || valueArg;
  }
  if (formatterArg === 'energy') {
    const numeric = Number(valueArg);
    return Number.isFinite(numeric) ? Math.round(numeric / 100) / 10 : valueArg;
  }
  if (formatterArg === 'round') {
    return roundValue(valueArg);
  }
  if (formatterArg === 'float') {
    const numeric = Number(valueArg);
    return Number.isFinite(numeric) ? Math.round(numeric * 1000) / 1000 : valueArg;
  }
  return valueArg;
};

const roundValue = (valueArg: unknown): number | unknown => {
  const numeric = Number(valueArg);
  return Number.isFinite(numeric) ? Math.round(numeric) : valueArg;
};

const deviceClassForUnit = (unitArg?: string): string | undefined => {
  if (unitArg === 'W') {
    return 'power';
  }
  if (unitArg === 'V') {
    return 'voltage';
  }
  if (unitArg === 'A') {
    return 'current';
  }
  if (unitArg === 'Wh' || unitArg === 'kWh') {
    return 'energy';
  }
  return undefined;
};

const endpointHost = (valueArg: string | undefined): string | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    return /^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg) ? new URL(valueArg).hostname : valueArg;
  } catch {
    return valueArg;
  }
};

const endpointPort = (valueArg: string | undefined): number | undefined => {
  if (!valueArg || !/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg)) {
    return undefined;
  }
  try {
    const url = new URL(valueArg);
    return url.port ? Number(url.port) : undefined;
  } catch {
    return undefined;
  }
};

const titleCase = (valueArg: string): string => {
  return valueArg.replace(/[:_-]+/g, ' ').replace(/\b\w/g, (charArg) => charArg.toUpperCase());
};

const cleanAttributes = (attributesArg: Record<string, unknown>): Record<string, unknown> | undefined => {
  const clean = Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  return Object.keys(clean).length ? clean : undefined;
};
