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 { IObihaiConfig, IObihaiRawData } from './obihai.types.js';
import { obihaiDefaultPort, obihaiProfile } from './obihai.types.js';

export class ObihaiMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IObihaiConfig>, 'profile'>): ISimpleLocalSnapshot {
    const rawData = optionsArg.rawData ?? optionsArg.config.rawData;
    if (this.isNativeRawData(rawData)) {
      return this.toNativeSnapshot(optionsArg.config, rawData, optionsArg.online ?? true, optionsArg.source || 'manual', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: obihaiProfile });
  }

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

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

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

  private static toNativeSnapshot(configArg: IObihaiConfig, rawDataArg: IObihaiRawData, onlineArg: boolean, sourceArg: ISimpleLocalSnapshot['source'], errorArg?: string): ISimpleLocalSnapshot {
    const endpoint = this.endpointInfo(configArg);
    const deviceInfo = this.recordValue(rawDataArg.deviceInfo) || {};
    const entities: ISimpleLocalEntitySnapshot[] = [];

    for (const [key, value] of Object.entries(this.recordValue(rawDataArg.services) || {})) {
      entities.push(this.entityFromValue(key, value, sourceArg));
    }
    for (const [key, value] of Object.entries(this.recordValue(rawDataArg.lineServices) || {})) {
      entities.push(this.entityFromValue(key, value, sourceArg));
    }
    for (const [key, value] of Object.entries(this.recordValue(rawDataArg.callDirection) || {})) {
      entities.push(this.entityFromValue(key, value, sourceArg));
    }
    entities.push({
      id: 'reboot',
      name: 'Reboot',
      platform: 'button',
      state: false,
      writable: true,
      available: onlineArg,
      attributes: { source: sourceArg },
    });

    const serialNumber = this.stringValue(deviceInfo.SerialNumber);
    const modelName = this.stringValue(deviceInfo.ModelName);
    const macAddress = this.stringValue(deviceInfo.MACAddress) || configArg.macAddress;

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || serialNumber || macAddress || (endpoint.host ? `${endpoint.host}:${endpoint.port || obihaiDefaultPort}` : undefined) || configArg.name || obihaiProfile.domain,
        name: configArg.name || modelName || obihaiProfile.defaultName || obihaiProfile.displayName,
        manufacturer: obihaiProfile.manufacturer,
        model: modelName || obihaiProfile.model,
        serialNumber,
        host: endpoint.host,
        port: endpoint.port,
        protocol: 'http',
        configurationUrl: endpoint.configurationUrl,
        attributes: this.cleanAttributes({
          macAddress,
          hardwareVersion: this.stringValue(deviceInfo.HardwareVersion),
          softwareVersion: this.stringValue(deviceInfo.SoftwareVersion),
          fetchedAt: rawDataArg.fetchedAt,
          ...configArg.metadata,
        }),
      },
      entities,
      online: onlineArg,
      updatedAt: new Date().toISOString(),
      source: sourceArg,
      rawData: this.clone(rawDataArg),
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: true,
        protocol: 'http-digest',
      },
    };
  }

  private static entityFromValue(keyArg: string, valueArg: unknown, sourceArg: ISimpleLocalSnapshot['source']): ISimpleLocalEntitySnapshot {
    return {
      id: this.slug(keyArg),
      name: keyArg,
      platform: 'sensor',
      state: valueArg instanceof Date ? valueArg.toISOString() : valueArg,
      deviceClass: keyArg === 'Last Reboot' ? 'timestamp' : undefined,
      available: true,
      writable: false,
      attributes: this.cleanAttributes({ source: sourceArg, iconHint: this.iconHint(keyArg, valueArg) }),
    };
  }

  private static isNativeRawData(valueArg: unknown): valueArg is IObihaiRawData {
    if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
      return false;
    }
    const value = valueArg as Record<string, unknown>;
    if ('device' in value && 'entities' in value) {
      return false;
    }
    return 'deviceInfo' in value || 'services' in value || 'lineServices' in value || 'callDirection' in value;
  }

  private static endpointInfo(configArg: IObihaiConfig): { host?: string; port?: number; configurationUrl?: string } {
    const parsed = this.parseUrl(configArg.url);
    const host = configArg.host || parsed?.hostname;
    const port = configArg.port || (parsed?.port ? Number(parsed.port) : undefined) || (host ? obihaiDefaultPort : undefined);
    const portPart = port && port !== 80 ? `:${port}` : '';
    return { host, port, configurationUrl: host ? `http://${host}${portPart}` : undefined };
  }

  private static parseUrl(valueArg: string | undefined): URL | undefined {
    if (!valueArg) {
      return undefined;
    }
    try {
      return new URL(valueArg);
    } catch {
      return undefined;
    }
  }

  private static recordValue(valueArg: unknown): Record<string, unknown> | undefined {
    return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : undefined;
  }

  private static stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
  }

  private static iconHint(keyArg: string, valueArg: unknown): string | undefined {
    const value = String(valueArg || '');
    if (keyArg === 'Call Direction') {
      return value === 'No Active Calls' ? 'phone-off' : value === 'Inbound Call' ? 'phone-incoming' : 'phone-outgoing';
    }
    if (keyArg.includes('Caller Info')) {
      return 'phone-log';
    }
    if (keyArg.includes('Port')) {
      return value === 'Ringing' ? 'phone-ring' : value === 'Off Hook' ? 'phone-in-talk' : 'phone-hangup';
    }
    if (keyArg.includes('Service Status')) {
      return value === '0' ? 'phone-hangup' : 'phone-in-talk';
    }
    if (keyArg.includes('Reboot Required')) {
      return value === 'false' ? 'restart-off' : 'restart-alert';
    }
    return undefined;
  }

  private static 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;
  }

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