import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalDeviceInfo, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData, type TSimpleLocalSnapshotSource } from '../../core/index.js';
import type { IOpenevseConfig, IOpenevseRawApiData } from './openevse.types.js';
import { openevseDefaultPort, openevseProfile } from './openevse.types.js';

export class OpenevseMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IOpenevseConfig>, 'profile'>): ISimpleLocalSnapshot {
    if (isOpenevseRawApiData(optionsArg.rawData)) {
      return openevseApiDataToSnapshot(optionsArg.config, optionsArg.rawData, optionsArg.online ?? true, optionsArg.source || 'http', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: openevseProfile });
  }

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

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

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

export const openevseApiDataToSnapshot = (configArg: IOpenevseConfig, rawDataArg: IOpenevseRawApiData, onlineArg: boolean, sourceArg: TSimpleLocalSnapshotSource, errorArg?: string): ISimpleLocalSnapshot => {
  const endpoint = endpointInfo(configArg);
  const config = rawDataArg.config || {};
  const status = rawDataArg.status || {};
  const serialNumber = stringValue(config.wifi_serial) || configArg.serialNumber || configArg.macAddress;
  const device: ISimpleLocalDeviceInfo = {
    id: configArg.uniqueId || configArg.deviceId || serialNumber || endpoint?.id || openevseProfile.domain,
    name: configArg.name || stringValue(config.hostname) || openevseProfile.defaultName || openevseProfile.displayName,
    manufacturer: openevseProfile.manufacturer,
    model: stringValue(config.buildenv) || openevseProfile.model,
    serialNumber,
    host: endpoint?.host,
    port: endpoint?.port,
    protocol: endpoint?.protocol || openevseProfile.defaultProtocol,
    configurationUrl: endpoint?.origin,
    attributes: cleanAttributes({
      firmware: stringValue(config.firmware),
      wifiFirmware: stringValue(config.version),
      protocol: config.protocol === '-' ? undefined : stringValue(config.protocol),
      ipAddress: stringValue(status.ipaddress),
      ethernet: booleanValue(status.eth_connected),
    }),
  };

  return {
    device,
    entities: rawDataArg.entities?.length ? rawDataArg.entities : openevseEntities(status, config),
    online: onlineArg,
    updatedAt: rawDataArg.fetchedAt || new Date().toISOString(),
    source: sourceArg,
    rawData: rawDataArg,
    error: errorArg,
    capabilities: {
      localRead: true,
      localControl: true,
      dataPaths: ['/status', '/config'],
      controlPaths: ['/override', '/r'],
    },
  };
};

export const isOpenevseRawApiData = (valueArg: unknown): valueArg is IOpenevseRawApiData => {
  if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
    return false;
  }
  const record = valueArg as Record<string, unknown>;
  return isRecord(record.status) && isRecord(record.config);
};

const openevseEntities = (statusArg: Record<string, unknown>, configArg: Record<string, unknown>): ISimpleLocalEntitySnapshot[] => {
  const entities = [
    sensor('status', 'Status', statusText(statusArg), { deviceClass: 'enum' }),
    sensor('service_level', 'Service Level', serviceLevel(configArg.service), { deviceClass: 'enum' }),
    sensor('charge_time', 'Charge Time', numberValue(statusArg.elapsed), { unit: 's', deviceClass: 'duration', stateClass: 'measurement' }),
    sensor('vehicle_eta', 'Vehicle ETA', vehicleEta(statusArg), { deviceClass: 'timestamp' }),
    sensor('charging_current', 'Charging Current', numberValue(statusArg.amp), { unit: 'mA', deviceClass: 'current', stateClass: 'measurement' }),
    sensor('charging_voltage', 'Charging Voltage', numberValue(statusArg.voltage), { unit: 'V', deviceClass: 'voltage', stateClass: 'measurement' }),
    sensor('charging_power', 'Charging Power', chargingPower(statusArg), { unit: 'mW', deviceClass: 'power', stateClass: 'measurement' }),
    sensor('current_power', 'Current Power', numberValue(statusArg.power), { unit: 'W', deviceClass: 'power', stateClass: 'measurement' }),
    sensor('current_capacity', 'Current Capacity', numberValue(statusArg.pilot), { unit: 'A', deviceClass: 'current', stateClass: 'measurement' }),
    sensor('max_current', 'Max Current', numberValue(statusArg.max_current), { unit: 'A', deviceClass: 'current' }),
    sensor('min_amps', 'Minimum Amps', numberValue(configArg.min_current_hard) ?? 6, { unit: 'A', deviceClass: 'current' }),
    sensor('max_amps', 'Maximum Amps', numberValue(configArg.max_current_hard) ?? 80, { unit: 'A', deviceClass: 'current' }),
    sensor('ambient_temp', 'Ambient Temperature', ambientTemperature(statusArg), { unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' }),
    sensor('ir_temp', 'IR Temperature', dividedTemperature(statusArg.temp3), { unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' }),
    sensor('rtc_temp', 'RTC Temperature', dividedTemperature(statusArg.temp2), { unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' }),
    sensor('esp_temp', 'ESP Temperature', dividedTemperature(statusArg.temp4), { unit: 'C', deviceClass: 'temperature', stateClass: 'measurement' }),
    sensor('usage_session', 'Usage Session', usageSession(statusArg), { unit: 'Wh', deviceClass: 'energy', stateClass: 'total_increasing' }),
    sensor('usage_total', 'Usage Total', numberValue(statusArg.total_energy) ?? numberValue(statusArg.watthour), { unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' }),
    sensor('total_day', 'Total Day', numberValue(statusArg.total_day), { unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' }),
    sensor('total_week', 'Total Week', numberValue(statusArg.total_week), { unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' }),
    sensor('total_month', 'Total Month', numberValue(statusArg.total_month), { unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' }),
    sensor('total_year', 'Total Year', numberValue(statusArg.total_year), { unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' }),
    sensor('vehicle_soc', 'Vehicle SOC', numberValue(statusArg.vehicle_soc) ?? numberValue(statusArg.battery_level), { unit: '%', deviceClass: 'battery', stateClass: 'measurement' }),
    sensor('vehicle_range', 'Vehicle Range', numberValue(statusArg.vehicle_range) ?? numberValue(statusArg.battery_range), { unit: 'km', deviceClass: 'distance', stateClass: 'measurement' }),
    sensor('wifi_signal', 'WiFi Signal', numberValue(statusArg.srssi), { unit: 'dBm', deviceClass: 'signal_strength', stateClass: 'measurement' }),
    sensor('shaper_live_power', 'Shaper Live Power', numberValue(statusArg.shaper_live_pwr), { unit: 'W', deviceClass: 'power', stateClass: 'measurement' }),
    sensor('shaper_available_current', 'Shaper Available Current', shaperAvailableCurrent(statusArg), { unit: 'A', deviceClass: 'current', stateClass: 'measurement' }),
    sensor('shaper_max_power', 'Shaper Max Power', numberValue(statusArg.shaper_max_pwr), { unit: 'W', deviceClass: 'power' }),
    sensor('gfi_trip_count', 'GFI Trip Count', numberValue(statusArg.gfcicount), { stateClass: 'total' }),
    sensor('no_gnd_trip_count', 'No Ground Trip Count', numberValue(statusArg.nogndcount), { stateClass: 'total' }),
    sensor('stuck_relay_trip_count', 'Stuck Relay Trip Count', numberValue(statusArg.stuckcount), { stateClass: 'total' }),
    sensor('uptime', 'Uptime', numberValue(statusArg.uptime), { unit: 's', deviceClass: 'duration' }),
    sensor('freeram', 'Free RAM', numberValue(statusArg.freeram), { unit: 'B', deviceClass: 'data_size' }),
    numberEntity('charge_rate', 'Charge Rate', numberValue(configArg.max_current_soft) ?? numberValue(statusArg.pilot), {
      min: numberValue(configArg.min_current_hard) ?? 6,
      max: numberValue(configArg.max_current_hard) ?? 80,
    }),
  ];
  return entities.filter((entityArg): entityArg is ISimpleLocalEntitySnapshot => Boolean(entityArg));
};

const sensor = (idArg: string, nameArg: string, stateArg: unknown, optionsArg: Partial<ISimpleLocalEntitySnapshot> = {}): ISimpleLocalEntitySnapshot | undefined => stateArg === undefined ? undefined : ({
  id: idArg,
  uniqueId: `${openevseProfile.domain}_${idArg}`,
  name: nameArg,
  platform: 'sensor',
  state: stateArg,
  available: true,
  writable: false,
  ...optionsArg,
});

const numberEntity = (idArg: string, nameArg: string, stateArg: unknown, optionsArg: { min: number; max: number }): ISimpleLocalEntitySnapshot | undefined => stateArg === undefined ? undefined : ({
  id: idArg,
  uniqueId: `${openevseProfile.domain}_${idArg}`,
  name: nameArg,
  platform: 'number',
  state: stateArg,
  available: true,
  writable: true,
  unit: 'A',
  deviceClass: 'current',
  attributes: { min: optionsArg.min, max: optionsArg.max, step: 1 },
});

const statusStates: Record<number, string> = {
  0: 'unknown',
  1: 'not connected',
  2: 'connected',
  3: 'charging',
  4: 'vent required',
  5: 'diode check failed',
  6: 'gfci fault',
  7: 'no ground',
  8: 'stuck relay',
  9: 'gfci self-test failure',
  10: 'over temperature',
  254: 'sleeping',
  255: 'disabled',
};

const statusText = (statusArg: Record<string, unknown>): string => stringValue(statusArg.status) || statusStates[numberValue(statusArg.state) ?? 0] || 'unknown';
const serviceLevel = (valueArg: unknown): string | undefined => ({ '1': 'level_1', '2': 'level_2', a: 'automatic' }[String(valueArg || '').toLowerCase()]);
const chargingPower = (statusArg: Record<string, unknown>): number | undefined => {
  const voltage = numberValue(statusArg.voltage);
  const amp = numberValue(statusArg.amp);
  return voltage === undefined || amp === undefined ? undefined : Math.round(voltage * amp * 100) / 100;
};
const ambientTemperature = (statusArg: Record<string, unknown>): number | undefined => dividedTemperature(statusArg.temp) ?? dividedTemperature(statusArg.temp1);
const dividedTemperature = (valueArg: unknown): number | undefined => {
  const value = numberValue(valueArg);
  return value === undefined ? undefined : value / 10;
};
const usageSession = (statusArg: Record<string, unknown>): number | undefined => numberValue(statusArg.session_energy) ?? (numberValue(statusArg.wattsec) === undefined ? undefined : Math.round((numberValue(statusArg.wattsec)! / 3600) * 100) / 100);
const shaperAvailableCurrent = (statusArg: Record<string, unknown>): number | undefined => numberValue(statusArg.shaper_cur) === 255 ? numberValue(statusArg.pilot) : numberValue(statusArg.shaper_cur);
const vehicleEta = (statusArg: Record<string, unknown>): string | undefined => {
  const seconds = numberValue(statusArg.time_to_full_charge) ?? numberValue(statusArg.vehicle_eta);
  return seconds === undefined ? undefined : new Date(Date.now() + seconds * 1000).toISOString();
};

const endpointInfo = (configArg: IOpenevseConfig): { id: string; host: string; port: number; protocol: 'http' | 'https'; origin: string } | undefined => {
  const rawUrl = stringValue(configArg.url) || stringValue(configArg.metadata?.url);
  const parsedUrl = rawUrl ? parseUrl(rawUrl) : undefined;
  const parsedHost = !parsedUrl && configArg.host ? parseUrl(configArg.host) : undefined;
  const url = parsedUrl || parsedHost;
  if (url) {
    const protocol = url.protocol === 'https:' ? 'https' : 'http';
    const port = configArg.port || numberValue(url.port) || (protocol === 'https' ? 443 : openevseDefaultPort);
    const origin = `${protocol}://${url.hostname}${portPart(protocol, port)}`;
    return { id: `${url.hostname}:${port}`, host: url.hostname, port, protocol, origin };
  }
  const host = stringValue(configArg.host);
  if (!host) {
    return undefined;
  }
  const protocol = configArg.useTls ? 'https' : 'http';
  const port = configArg.port || (protocol === 'https' ? 443 : openevseDefaultPort);
  const origin = `${protocol}://${host}${portPart(protocol, port)}`;
  return { id: `${host}:${port}`, host, port, protocol, origin };
};

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 => typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : stringValue(valueArg) && Number.isFinite(Number(valueArg)) ? Number(valueArg) : undefined;
const booleanValue = (valueArg: unknown): boolean | undefined => typeof valueArg === 'boolean' ? valueArg : undefined;
const parseUrl = (valueArg: string): URL | undefined => {
  if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg)) {
    return undefined;
  }
  try {
    return new URL(valueArg);
  } catch {
    return undefined;
  }
};
const portPart = (protocolArg: 'http' | 'https', portArg: number): string => (protocolArg === 'http' && portArg === 80) || (protocolArg === 'https' && portArg === 443) ? '' : `:${portArg}`;
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;
};
