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 { IGraphiteConfig, TGraphiteProtocol } from './graphite.types.js';
import { graphiteDefaultHost, graphiteDefaultName, graphiteDefaultPort, graphiteDefaultPrefix, graphiteDefaultProtocol, graphiteProfile } from './graphite.types.js';

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

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

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

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

  private static normalizeRawData(configArg: IGraphiteConfig, rawDataArg: unknown): unknown {
    if (isRecord(rawDataArg) && 'device' in rawDataArg && 'entities' in rawDataArg) {
      return rawDataArg;
    }

    const rawObject = isRecord(rawDataArg) ? rawDataArg : undefined;
    const metrics = metricLines(configArg, rawDataArg);
    const host = configArg.host || stringValue(rawObject?.host) || graphiteDefaultHost;
    const port = configArg.port || numberValue(rawObject?.port) || graphiteDefaultPort;
    const protocol = protocolValue(configArg.protocol || stringValue(rawObject?.protocol)) || graphiteDefaultProtocol;
    const prefix = configArg.prefix || stringValue(rawObject?.prefix) || graphiteDefaultPrefix;
    const name = configArg.name || stringValue(rawObject?.name) || `${graphiteDefaultName} ${host}:${port}`;
    const uniqueBase = configArg.uniqueId || `${host}:${port}:${protocol}`;
    const hasHints = Boolean(rawObject || configArg.metrics?.length || configArg.host || configArg.prefix);
    if (!hasHints) {
      return rawDataArg;
    }

    return {
      device: {
        id: uniqueBase,
        name,
        manufacturer: graphiteProfile.manufacturer,
        model: graphiteProfile.model,
        host,
        port,
        protocol: protocol === 'tcp' ? 'tcp' : 'local',
        attributes: {
          graphiteProtocol: protocol,
          prefix,
        },
      },
      entities: graphiteEntities(uniqueBase, host, port, protocol, prefix, metrics),
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const graphiteEntities = (uniqueBaseArg: string, hostArg: string, portArg: number, protocolArg: TGraphiteProtocol, prefixArg: string, metricsArg: string[]): ISimpleLocalEntitySnapshot[] => [
  {
    id: 'configured',
    uniqueId: `${graphiteProfile.domain}_${SimpleLocalMapper.slug(uniqueBaseArg)}_configured`,
    name: 'Configured',
    platform: 'binary_sensor',
    state: Boolean(hostArg && portArg),
    available: true,
    writable: false,
    deviceClass: 'connectivity',
  },
  sensorEntity(uniqueBaseArg, 'metric_lines', 'Metric Lines', metricsArg.length, undefined, undefined, 'measurement'),
  sensorEntity(uniqueBaseArg, 'protocol', 'Protocol', protocolArg),
  sensorEntity(uniqueBaseArg, 'prefix', 'Prefix', prefixArg),
  sensorEntity(uniqueBaseArg, 'last_metric', 'Last Metric', metricsArg[metricsArg.length - 1] || null, undefined, undefined, undefined, { entityRegistryEnabledDefault: false }),
];

const sensorEntity = (uniqueBaseArg: string, idArg: string, nameArg: string, stateArg: unknown, unitArg?: string, deviceClassArg?: string, stateClassArg?: string, attributesArg?: Record<string, unknown>): ISimpleLocalEntitySnapshot => ({
  id: idArg,
  uniqueId: `${graphiteProfile.domain}_${SimpleLocalMapper.slug(uniqueBaseArg)}_${SimpleLocalMapper.slug(idArg)}`,
  name: nameArg,
  platform: 'sensor',
  state: stateArg,
  available: stateArg !== undefined,
  writable: false,
  unit: unitArg,
  deviceClass: deviceClassArg,
  stateClass: stateClassArg,
  attributes: attributesArg,
});

const metricLines = (configArg: IGraphiteConfig, rawDataArg: unknown): string[] => {
  if (configArg.metrics?.length) {
    return configArg.metrics;
  }
  if (typeof rawDataArg === 'string') {
    return rawDataArg.split('\n').map((lineArg) => lineArg.trim()).filter(Boolean);
  }
  const rawObject = recordValue(rawDataArg);
  const value = rawObject?.metrics ?? rawObject?.metricLines ?? rawObject?.metric_lines ?? rawObject?.lines;
  if (Array.isArray(value)) {
    return value.filter((lineArg): lineArg is string => typeof lineArg === 'string' && lineArg.trim().length > 0).map((lineArg) => lineArg.trim());
  }
  if (typeof value === 'string') {
    return value.split('\n').map((lineArg) => lineArg.trim()).filter(Boolean);
  }
  return [];
};

const protocolValue = (valueArg: unknown): TGraphiteProtocol | undefined => {
  const value = stringValue(valueArg)?.toLowerCase();
  return value === 'tcp' || value === 'udp' ? value : undefined;
};

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 stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
const numberValue = (valueArg: unknown): number | undefined => {
  const value = typeof valueArg === 'number' ? valueArg : typeof valueArg === 'string' && valueArg.trim() ? Number(valueArg) : undefined;
  return value !== undefined && Number.isFinite(value) ? value : undefined;
};
