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 { IOpenhardwaremonitorConfig, IOpenhardwaremonitorRawApiData } from './openhardwaremonitor.types.js';
import { openhardwaremonitorDefaultPort, openhardwaremonitorProfile } from './openhardwaremonitor.types.js';

export class OpenhardwaremonitorMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IOpenhardwaremonitorConfig>, 'profile'>): ISimpleLocalSnapshot {
    if (isOpenhardwaremonitorRawApiData(optionsArg.rawData)) {
      return openhardwaremonitorApiDataToSnapshot(optionsArg.config, optionsArg.rawData, optionsArg.online ?? true, optionsArg.source || 'http', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: openhardwaremonitorProfile });
  }

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

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

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

export const openhardwaremonitorApiDataToSnapshot = (configArg: IOpenhardwaremonitorConfig, rawDataArg: IOpenhardwaremonitorRawApiData, onlineArg: boolean, sourceArg: TSimpleLocalSnapshotSource, errorArg?: string): ISimpleLocalSnapshot => {
  const endpoint = endpointInfo(configArg);
  const data = rawDataArg.data || {};
  const computerName = firstComputerName(data) || stringValue(data.Text);
  const entities = rawDataArg.entities?.length ? rawDataArg.entities : openhardwaremonitorEntities(data);
  const device: ISimpleLocalDeviceInfo = {
    id: configArg.uniqueId || configArg.deviceId || computerName || endpoint?.id || openhardwaremonitorProfile.domain,
    name: configArg.name || computerName || openhardwaremonitorProfile.defaultName || openhardwaremonitorProfile.displayName,
    manufacturer: openhardwaremonitorProfile.manufacturer,
    model: openhardwaremonitorProfile.model,
    host: endpoint?.host,
    port: endpoint?.port,
    protocol: endpoint?.protocol || openhardwaremonitorProfile.defaultProtocol,
    configurationUrl: endpoint?.origin,
    attributes: cleanAttributes({
      rootName: stringValue(data.Text),
      sensorCount: entities.length,
    }),
  };

  return {
    device,
    entities,
    online: onlineArg,
    updatedAt: rawDataArg.fetchedAt || new Date().toISOString(),
    source: sourceArg,
    rawData: rawDataArg,
    error: errorArg,
    capabilities: {
      localRead: true,
      localControl: false,
      dataPath: '/data.json',
    },
  };
};

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

const openhardwaremonitorEntities = (rootArg: Record<string, unknown>): ISimpleLocalEntitySnapshot[] => {
  const sensors = flattenSensors(rootArg, [], true);
  return sensors.map((sensorArg): ISimpleLocalEntitySnapshot => ({
    id: slug(sensorArg.name || sensorArg.path.join('_')),
    uniqueId: `${openhardwaremonitorProfile.domain}_${slug(sensorArg.name || sensorArg.path.join('_'))}`,
    name: sensorArg.name,
    platform: 'sensor',
    state: sensorArg.value,
    available: sensorArg.value !== null,
    writable: false,
    unit: sensorArg.unit,
    stateClass: 'measurement',
    attributes: cleanAttributes({
      name: sensorArg.leafName,
      minimal_value: sensorArg.min,
      maximum_value: sensorArg.max,
      path: sensorArg.path.join('/'),
      ...Object.fromEntries(sensorArg.levels.map((levelArg, indexArg) => [`level_${indexArg}`, levelArg])),
    }),
  }));
};

const flattenSensors = (nodeArg: Record<string, unknown>, parentNamesArg: string[], isRootArg: boolean): IOpenhardwaremonitorParsedSensor[] => {
  const children = arrayValue(nodeArg.Children).map((childArg) => isRecord(childArg) ? childArg : undefined).filter((childArg): childArg is Record<string, unknown> => Boolean(childArg));
  const nodeName = stringValue(nodeArg.Text) || 'Node';
  if (children.length) {
    const nextParents = isRootArg ? parentNamesArg : [...parentNamesArg, nodeName];
    return children.flatMap((childArg) => flattenSensors(childArg, nextParents, false));
  }

  const display = splitDisplayValue(stringValue(nodeArg.Value));
  if (!display.unit) {
    return [];
  }
  const nameParts = [...parentNamesArg, nodeName];
  const value = display.value === '-' ? null : display.value;
  return [{
    name: nameParts.join(' '),
    leafName: nodeName,
    value,
    min: splitDisplayValue(stringValue(nodeArg.Min)).value,
    max: splitDisplayValue(stringValue(nodeArg.Max)).value,
    unit: display.unit,
    levels: parentNamesArg,
    path: nameParts,
  }];
};

interface IOpenhardwaremonitorParsedSensor {
  name: string;
  leafName: string;
  value: string | null;
  min: string | null;
  max: string | null;
  unit?: string;
  levels: string[];
  path: string[];
}

const firstComputerName = (dataArg: Record<string, unknown>): string | undefined => {
  const first = arrayValue(dataArg.Children).map((childArg) => isRecord(childArg) ? childArg : undefined).find(Boolean);
  return first ? stringValue(first.Text) : undefined;
};

const splitDisplayValue = (valueArg: string | undefined): { value: string | null; unit?: string } => {
  if (!valueArg) {
    return { value: null };
  }
  const normalized = valueArg.replace(',', '.');
  const parts = normalized.split(' ');
  return { value: parts[0] || null, unit: parts[1] };
};

const endpointInfo = (configArg: IOpenhardwaremonitorConfig): { 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 : openhardwaremonitorDefaultPort);
    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 : openhardwaremonitorDefaultPort);
  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 arrayValue = (valueArg: unknown): unknown[] => Array.isArray(valueArg) ? 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 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 slug = (valueArg: string): string => valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'sensor';
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;
};
