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 { ILocalIpConfig } from './local_ip.types.js';
import { localIpProfile } from './local_ip.types.js';

export class LocalIpMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<ILocalIpConfig>, 'profile'>): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({
      ...optionsArg,
      profile: localIpProfile,
      rawData: this.normalizeRawData(optionsArg.config, optionsArg.rawData, optionsArg.source),
    });
  }

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

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

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

  private static normalizeRawData(configArg: ILocalIpConfig, rawDataArg: unknown, sourceArg?: ISimpleLocalSnapshotOptions<ILocalIpConfig>['source']): unknown {
    if (isRecord(rawDataArg) && 'device' in rawDataArg && 'entities' in rawDataArg) {
      return rawDataArg;
    }

    const rawObject = isRecord(rawDataArg) ? rawDataArg : undefined;
    const address = stringValue(rawObject?.address) || stringValue(rawObject?.localAddress) || stringValue(rawObject?.sourceIp) || stringValue(rawObject?.ip) || (typeof rawDataArg === 'string' ? rawDataArg : undefined);
    if (!address) {
      return rawDataArg;
    }

    const targetHost = stringValue(rawObject?.targetHost) || stringValue(rawObject?.target_host) || stringValue(configArg.targetHost) || stringValue(configArg.target_host) || stringValue(configArg.host);
    const targetPort = numberValue(rawObject?.targetPort) ?? numberValue(rawObject?.target_port) ?? numberValue(configArg.targetPort) ?? numberValue(configArg.target_port) ?? numberValue(configArg.port);
    const name = configArg.name || localIpProfile.defaultName || localIpProfile.displayName;
    const uniqueBase = SimpleLocalMapper.slug(configArg.uniqueId || address || name);
    const entities: ISimpleLocalEntitySnapshot[] = [{
      id: 'address',
      uniqueId: `${localIpProfile.domain}_${uniqueBase}_address`,
      name: 'IP Address',
      platform: 'sensor',
      state: address,
      available: true,
      writable: false,
      attributes: {
        family: stringValue(rawObject?.family) || (address.includes(':') ? 'IPv6' : 'IPv4'),
        targetHost,
        targetPort,
        localPort: numberValue(rawObject?.localPort),
        protocol: stringValue(rawObject?.protocol) || 'tcp',
      },
    }];

    return {
      device: {
        id: configArg.uniqueId || address,
        name,
        manufacturer: localIpProfile.manufacturer,
        model: localIpProfile.model,
        host: address,
        protocol: 'tcp',
        attributes: {
          targetHost,
          targetPort,
        },
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: sourceArg || 'tcp',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

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) ? Math.round(value) : 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;
