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 { IEmoncmsHistoryConfig } from './emoncms_history.types.js';
import { emoncmsHistoryDefaultName, emoncmsHistoryProfile } from './emoncms_history.types.js';

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

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

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

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

  private static normalizeRawData(configArg: IEmoncmsHistoryConfig, rawDataArg: unknown): unknown {
    if (!isRecord(rawDataArg) || isSnapshotLike(rawDataArg) || hasSimpleEntities(rawDataArg)) {
      return rawDataArg;
    }

    const payload = recordValue(rawDataArg.payload_dict) || recordValue(rawDataArg.payload) || recordValue(rawDataArg.data) || recordValue(rawDataArg.values) || recordValue(rawDataArg.states) || primitiveRecord(rawDataArg);
    const readings = Object.entries(payload).filter((entryArg): entryArg is [string, string | number | boolean | null] => isPrimitive(entryArg[1]));
    if (!readings.length) {
      return rawDataArg;
    }

    const endpoint = endpointInfo(configArg, rawDataArg);
    const inputNode = configArg.inputNode ?? configArg.inputnode ?? rawDataArg.inputnode ?? rawDataArg.inputNode ?? rawDataArg.node;
    const whitelist = stringArray(configArg.whitelist) || stringArray(rawDataArg.whitelist);
    const units = recordValue(rawDataArg.units) || {};
    const name = configArg.name || stringValue(rawDataArg.name) || emoncmsHistoryDefaultName;
    const entities: ISimpleLocalEntitySnapshot[] = readings.map(([keyArg, valueArg]) => ({
      id: SimpleLocalMapper.slug(keyArg),
      uniqueId: `${emoncmsHistoryProfile.domain}_${SimpleLocalMapper.slug(endpoint.host || name)}_${SimpleLocalMapper.slug(keyArg)}`,
      name: titleCase(keyArg),
      platform: 'sensor',
      state: valueArg,
      available: true,
      writable: false,
      unit: stringValue(units[keyArg]),
      attributes: {
        entityId: keyArg,
        inputNode,
        whitelisted: whitelist ? whitelist.includes(keyArg) : undefined,
      },
    }));

    return {
      device: {
        id: configArg.uniqueId || (endpoint.host ? `${endpoint.host}:${endpoint.port || ''}` : undefined) || stringValue(inputNode) || name,
        name,
        manufacturer: emoncmsHistoryProfile.manufacturer,
        model: emoncmsHistoryProfile.model,
        host: endpoint.host,
        port: endpoint.port,
        protocol: endpoint.useTls ? 'https' : emoncmsHistoryProfile.defaultProtocol,
        configurationUrl: endpoint.url,
        attributes: {
          inputNode,
          scanInterval: configArg.scanInterval ?? configArg.scan_interval ?? rawDataArg.scan_interval ?? rawDataArg.scanInterval,
          whitelist,
        },
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const ignoredPayloadKeys = new Set(['api_key', 'apiKey', 'client', 'commandExecutor', 'device', 'host', 'inputnode', 'inputNode', 'metadata', 'node', 'password', 'scan_interval', 'scanInterval', 'snapshot', 'snapshotProvider', 'token', 'units', 'url', 'username', 'whitelist']);

const primitiveRecord = (valueArg: Record<string, unknown>): Record<string, unknown> => Object.fromEntries(Object.entries(valueArg).filter(([keyArg, rawValueArg]) => !ignoredPayloadKeys.has(keyArg) && isPrimitive(rawValueArg)));

const endpointInfo = (configArg: IEmoncmsHistoryConfig, rawDataArg: Record<string, unknown>): { host?: string; port?: number; useTls?: boolean; url?: string } => {
  const urlValue = configArg.url || stringValue(rawDataArg.url);
  const parsed = parseUrl(urlValue);
  return {
    host: configArg.host || parsed?.host,
    port: configArg.port || parsed?.port,
    useTls: configArg.useTls ?? parsed?.useTls,
    url: parsed?.url || urlValue,
  };
};

const parseUrl = (valueArg?: string): { host: string; port?: number; useTls: boolean; url: string } | undefined => {
  if (!valueArg || !/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg)) {
    return undefined;
  }
  try {
    const url = new URL(valueArg);
    return { host: url.hostname, port: url.port ? Number(url.port) : undefined, useTls: url.protocol === 'https:', url: url.toString() };
  } catch {
    return undefined;
  }
};

const hasSimpleEntities = (valueArg: Record<string, unknown>): boolean => Array.isArray(valueArg.entities) && valueArg.entities.some((entityArg) => isRecord(entityArg) && 'name' in entityArg && 'state' in entityArg);
const isSnapshotLike = (valueArg: Record<string, unknown>): boolean => isRecord(valueArg.device) && Array.isArray(valueArg.entities);
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 isPrimitive = (valueArg: unknown): valueArg is string | number | boolean | null => valueArg === null || ['string', 'number', 'boolean'].includes(typeof valueArg);
const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
const stringArray = (valueArg: unknown): string[] | undefined => Array.isArray(valueArg) ? valueArg.filter((itemArg): itemArg is string => typeof itemArg === 'string') : undefined;
const titleCase = (valueArg: string): string => valueArg.replace(/[_./-]+/g, ' ').replace(/\b\w/g, (matchArg) => matchArg.toUpperCase());
