import * as plugins from '../../plugins.js';
import type { IIntegrationEntity } from '../../core/types.js';
import type { IHddtempConfig, IHddtempDiskSnapshot, IHddtempRawDiskInput, IHddtempSnapshot, THddtempRawData, THddtempSnapshotSource } from './hddtemp.types.js';
import { hddtempDefaultName, hddtempDefaultPort, hddtempDisplayName, hddtempDomain } from './hddtemp.types.js';

interface IHddtempSnapshotOptions {
  config: IHddtempConfig;
  rawData?: THddtempRawData;
  online?: boolean;
  source?: THddtempSnapshotSource;
  error?: string;
}

export class HddtempMapper {
  public static toSnapshot(optionsArg: IHddtempSnapshotOptions): IHddtempSnapshot {
    if (optionsArg.config.snapshot && optionsArg.rawData === undefined) {
      const snapshot = this.normalizeSnapshot(this.clone(optionsArg.config.snapshot), optionsArg.config, optionsArg.source || 'snapshot');
      return {
        ...snapshot,
        online: optionsArg.online ?? snapshot.online,
        source: optionsArg.source || snapshot.source,
        error: optionsArg.error ?? snapshot.error,
        updatedAt: optionsArg.error ? new Date().toISOString() : snapshot.updatedAt,
      };
    }

    const rawData = optionsArg.rawData ?? optionsArg.config.rawData ?? optionsArg.config.rawText ?? optionsArg.config.rawDisks;
    const rawDisks = rawData === undefined ? [] : this.parseRawData(rawData);
    const configuredDisks = this.configuredDisks(optionsArg.config.disks);
    const host = optionsArg.config.host;
    const port = optionsArg.config.port || (host ? hddtempDefaultPort : undefined);
    const name = optionsArg.config.name || hddtempDefaultName;
    const firstDisk = rawDisks[0];
    const id = optionsArg.config.uniqueId || (host ? `${host}:${port}` : undefined) || firstDisk?.device || name || hddtempDomain;
    const online = optionsArg.online ?? optionsArg.config.online ?? rawDisks.length > 0;

    return this.normalizeSnapshot({
      daemon: {
        id,
        name,
        host,
        port,
        manufacturer: optionsArg.config.manufacturer || hddtempDisplayName,
        model: optionsArg.config.model || 'hddtemp daemon',
        configuredDisks: configuredDisks.length ? configuredDisks : undefined,
      },
      disks: this.disksFromRaw(rawDisks, configuredDisks, optionsArg.config),
      online,
      updatedAt: new Date().toISOString(),
      source: optionsArg.source || (rawData === undefined ? 'runtime' : 'manual'),
      raw: typeof rawData === 'string' ? rawData : undefined,
      error: optionsArg.error,
    }, optionsArg.config, optionsArg.source || (rawData === undefined ? 'runtime' : 'manual'));
  }

  public static parseRawData(rawDataArg: THddtempRawData): IHddtempRawDiskInput[] {
    if (typeof rawDataArg === 'string') {
      return this.parseDaemonOutput(rawDataArg);
    }
    return rawDataArg.map((diskArg) => this.normalizeRawDisk(diskArg)).filter((diskArg): diskArg is IHddtempRawDiskInput => Boolean(diskArg));
  }

  public static parseDaemonOutput(textArg: string): IHddtempRawDiskInput[] {
    const trimmed = textArg.trim();
    if (!trimmed) {
      return [];
    }

    const records: IHddtempRawDiskInput[] = [];
    for (const recordText of trimmed.replace(/^\|/, '').replace(/\|$/, '').split('||')) {
      const fields = recordText.split('|');
      const device = fields[0]?.trim();
      if (!device) {
        continue;
      }
      records.push({
        device,
        model: fields[1]?.trim() || undefined,
        temperature: fields[2]?.trim() || undefined,
        unit: fields[3]?.trim() || undefined,
        raw: recordText,
      });
    }
    return records;
  }

  public static toDevices(snapshotArg: IHddtempSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    return [{
      id: this.daemonDeviceId(snapshotArg),
      integrationDomain: hddtempDomain,
      name: snapshotArg.daemon.name,
      protocol: (snapshotArg.daemon.host ? 'tcp' : 'unknown') as plugins.shxInterfaces.data.TDeviceProtocol,
      manufacturer: snapshotArg.daemon.manufacturer,
      model: snapshotArg.daemon.model,
      online: snapshotArg.online,
      features: snapshotArg.disks.map((diskArg) => ({
        id: this.temperatureFeatureId(diskArg),
        capability: 'sensor',
        name: `${diskArg.device} Temperature`,
        readable: true,
        writable: false,
        unit: diskArg.unit,
      })),
      state: snapshotArg.disks.map((diskArg) => ({
        featureId: this.temperatureFeatureId(diskArg),
        value: diskArg.temperature ?? null,
        updatedAt,
      })),
      metadata: this.cleanAttributes({
        host: snapshotArg.daemon.host,
        port: snapshotArg.daemon.port,
        source: snapshotArg.source,
        disks: snapshotArg.disks.map((diskArg) => diskArg.device),
        configuredDisks: snapshotArg.daemon.configuredDisks,
        error: snapshotArg.error,
      }),
    }];
  }

  public static toEntities(snapshotArg: IHddtempSnapshot): IIntegrationEntity[] {
    const deviceId = this.daemonDeviceId(snapshotArg);
    const uniqueBase = this.uniqueBase(snapshotArg);
    return snapshotArg.disks.map((diskArg) => ({
      id: `sensor.${this.slug(`${snapshotArg.daemon.name}_${diskArg.device}`)}`,
      uniqueId: `${hddtempDomain}_${uniqueBase}_${this.slug(diskArg.device)}_temperature`,
      integrationDomain: hddtempDomain,
      deviceId,
      platform: 'sensor' as const,
      name: `${snapshotArg.daemon.name} ${diskArg.device}`,
      state: diskArg.temperature ?? null,
      available: snapshotArg.online && diskArg.available,
      attributes: this.cleanAttributes({
        device: diskArg.device,
        model: diskArg.model,
        unit: diskArg.unit,
        deviceClass: 'temperature',
        stateClass: diskArg.temperature === undefined ? undefined : 'measurement',
        rawTemperature: diskArg.rawTemperature,
        source: snapshotArg.source,
        error: snapshotArg.error,
      }),
    }));
  }

  public static daemonDeviceId(snapshotArg: IHddtempSnapshot): string {
    return `${hddtempDomain}.daemon.${this.uniqueBase(snapshotArg)}`;
  }

  public static slug(valueArg: string): string {
    return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || hddtempDomain;
  }

  public static clone<TValue>(valueArg: TValue): TValue {
    return JSON.parse(JSON.stringify(valueArg)) as TValue;
  }

  private static normalizeSnapshot(snapshotArg: IHddtempSnapshot, configArg: IHddtempConfig, sourceArg: THddtempSnapshotSource): IHddtempSnapshot {
    const configuredDisks = this.configuredDisks(configArg.disks || snapshotArg.daemon.configuredDisks);
    const disksByDevice = new Map(snapshotArg.disks.map((diskArg) => [diskArg.device, diskArg]));
    const disks = configuredDisks.length
      ? configuredDisks.map((deviceArg) => disksByDevice.get(deviceArg) || this.missingDisk(deviceArg, configArg))
      : snapshotArg.disks;
    const host = snapshotArg.daemon.host || configArg.host;
    const port = snapshotArg.daemon.port || configArg.port || (host ? hddtempDefaultPort : undefined);
    const id = snapshotArg.daemon.id || configArg.uniqueId || (host ? `${host}:${port}` : undefined) || configArg.name || hddtempDomain;

    return {
      ...snapshotArg,
      daemon: {
        ...snapshotArg.daemon,
        id,
        name: snapshotArg.daemon.name || configArg.name || hddtempDefaultName,
        host,
        port,
        manufacturer: snapshotArg.daemon.manufacturer || configArg.manufacturer || hddtempDisplayName,
        model: snapshotArg.daemon.model || configArg.model || 'hddtemp daemon',
        configuredDisks: configuredDisks.length ? configuredDisks : undefined,
      },
      disks,
      updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
      source: snapshotArg.source || sourceArg,
    };
  }

  private static disksFromRaw(rawDisksArg: IHddtempRawDiskInput[], configuredDisksArg: string[], configArg: IHddtempConfig): IHddtempDiskSnapshot[] {
    const byDevice = new Map<string, IHddtempRawDiskInput>();
    for (const disk of rawDisksArg) {
      if (!byDevice.has(disk.device)) {
        byDevice.set(disk.device, disk);
      }
    }
    const devices = configuredDisksArg.length ? configuredDisksArg : [...byDevice.keys()];
    return devices.map((deviceArg) => {
      const disk = byDevice.get(deviceArg);
      return disk ? this.diskFromRaw(disk, configArg) : this.missingDisk(deviceArg, configArg);
    });
  }

  private static diskFromRaw(rawDiskArg: IHddtempRawDiskInput, configArg: IHddtempConfig): IHddtempDiskSnapshot {
    const rawTemperature = rawDiskArg.temperature === undefined || rawDiskArg.temperature === null ? undefined : String(rawDiskArg.temperature).trim();
    const temperature = this.temperatureValue(rawTemperature);
    return {
      id: this.slug(rawDiskArg.device),
      device: rawDiskArg.device,
      name: `${configArg.name || hddtempDefaultName} ${rawDiskArg.device}`,
      model: rawDiskArg.model,
      temperature,
      rawTemperature,
      unit: this.temperatureUnit(rawDiskArg.unit),
      available: true,
      raw: rawDiskArg.raw,
    };
  }

  private static missingDisk(deviceArg: string, configArg: IHddtempConfig): IHddtempDiskSnapshot {
    return {
      id: this.slug(deviceArg),
      device: deviceArg,
      name: `${configArg.name || hddtempDefaultName} ${deviceArg}`,
      unit: 'C',
      available: false,
    };
  }

  private static normalizeRawDisk(valueArg: IHddtempRawDiskInput): IHddtempRawDiskInput | undefined {
    const device = typeof valueArg.device === 'string' && valueArg.device.trim() ? valueArg.device.trim() : undefined;
    if (!device) {
      return undefined;
    }
    return {
      device,
      model: typeof valueArg.model === 'string' && valueArg.model.trim() ? valueArg.model.trim() : undefined,
      temperature: valueArg.temperature,
      unit: typeof valueArg.unit === 'string' && valueArg.unit.trim() ? valueArg.unit.trim() : undefined,
      raw: typeof valueArg.raw === 'string' ? valueArg.raw : undefined,
    };
  }

  private static configuredDisks(disksArg: string[] | undefined): string[] {
    return (disksArg || []).map((diskArg) => diskArg.trim()).filter(Boolean);
  }

  private static temperatureValue(valueArg: string | undefined): number | undefined {
    if (!valueArg) {
      return undefined;
    }
    const parsed = Number(valueArg);
    return Number.isFinite(parsed) ? parsed : undefined;
  }

  private static temperatureUnit(valueArg: string | undefined): 'C' | 'F' {
    return valueArg?.trim().toUpperCase() === 'F' ? 'F' : 'C';
  }

  private static temperatureFeatureId(diskArg: IHddtempDiskSnapshot): string {
    return `temperature_${this.slug(diskArg.device)}`;
  }

  private static uniqueBase(snapshotArg: IHddtempSnapshot): string {
    return this.slug(snapshotArg.daemon.id || snapshotArg.daemon.host || snapshotArg.daemon.name || hddtempDomain);
  }

  private static cleanAttributes(attributesArg: Record<string, unknown>): Record<string, unknown> {
    return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  }
}
