import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions } from '../../core/index.js';
import type { IMfiConfig, IMfiNativeRawData, IMfiPortSnapshot, TMfiRawData } from './mfi.types.js';
import { mfiDefaultHttpPort, mfiDefaultHttpsPort, mfiDefaultName, mfiProfile } from './mfi.types.js';

const mfiSensorModels = new Set(['Ubiquiti mFi-THS', 'Ubiquiti mFi-CS', 'Ubiquiti mFi-DS', 'Outlet', 'Input Analog', 'Input Digital']);
const mfiSwitchModels = new Set(['Outlet', 'Output 5v', 'Output 12v', 'Output 24v', 'Dimmer Switch']);
const mfiDigits: Record<string, number> = { volts: 1, amps: 1, active_power: 0, temperature: 1 };

export class MfiMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IMfiConfig>, 'profile'>): ISimpleLocalSnapshot {
    const rawData = optionsArg.rawData ?? optionsArg.config.rawData;
    if (this.isNativeRawData(rawData)) {
      return this.toSnapshotFromNativeRaw(optionsArg.config, rawData, optionsArg.online ?? true, optionsArg.source || 'http', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: mfiProfile });
  }

  public static toSnapshotFromRaw(configArg: IMfiConfig, rawDataArg: TMfiRawData): ISimpleLocalSnapshot {
    if (this.isNativeRawData(rawDataArg)) {
      return this.toSnapshotFromNativeRaw(configArg, rawDataArg, true, 'manual');
    }
    return SimpleLocalMapper.toSnapshot({ profile: mfiProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
  }

  public static toSnapshotFromNativeRaw(configArg: IMfiConfig, rawDataArg: IMfiNativeRawData, onlineArg = true, sourceArg: ISimpleLocalSnapshot['source'] = 'http', errorArg?: string): ISimpleLocalSnapshot {
    const ports = this.portsFromRawData(rawDataArg);
    const endpoint = this.endpoint(configArg);
    const primaryDevice = rawDataArg.devices?.[0] || {};
    const id = configArg.uniqueId || configArg.deviceId || stringValue(primaryDevice._id) || stringValue(primaryDevice.serial) || stringValue(primaryDevice.mac) || (endpoint.host ? `${endpoint.host}:${endpoint.port}` : undefined) || mfiProfile.domain;
    const name = configArg.name || stringValue(primaryDevice.name) || mfiDefaultName;
    const entities = this.entitiesFromPorts(ports, onlineArg, this.hasCommandTransport(configArg));

    return {
      device: {
        id,
        name,
        manufacturer: 'Ubiquiti',
        model: stringValue(primaryDevice.model) || mfiProfile.model,
        serialNumber: stringValue(primaryDevice.serial),
        host: endpoint.host,
        port: endpoint.port,
        protocol: endpoint.protocol,
        configurationUrl: endpoint.host ? `${endpoint.protocol}://${endpoint.host}${endpoint.port ? `:${endpoint.port}` : ''}` : undefined,
        attributes: cleanAttributes({
          controllerName: stringValue(primaryDevice.name),
          macAddress: stringValue(primaryDevice.mac) || configArg.macAddress,
          version: stringValue(primaryDevice.version),
          nativeDeviceCount: rawDataArg.devices?.length,
          nativeSensorCount: rawDataArg.sensors?.length,
        }),
      },
      entities: entities.length ? entities : [{
        id: 'ports',
        name: 'Ports',
        platform: 'sensor',
        state: ports.length,
        available: onlineArg,
        writable: false,
      }],
      online: onlineArg,
      updatedAt: rawDataArg.fetchedAt || new Date().toISOString(),
      source: sourceArg,
      rawData: rawDataArg,
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: this.hasCommandTransport(configArg),
        liveControlRequiresExecutor: !this.hasLiveEndpoint(configArg),
        protocol: 'mfi-http',
      },
    };
  }

  public static portsFromRawData(rawDataArg: IMfiNativeRawData): IMfiPortSnapshot[] {
    const sensors = Array.isArray(rawDataArg.sensors) ? rawDataArg.sensors : [];
    const ports: IMfiPortSnapshot[] = [];

    for (const device of rawDataArg.devices || []) {
      const portConfigs = Array.isArray(device.port_cfg) ? device.port_cfg as Array<Record<string, unknown>> : [];
      for (const portConfig of portConfigs) {
        const id = stringValue(portConfig._id);
        if (!id || id === 'NONE') {
          continue;
        }
        const sensor = sensors.find((sensorArg) => stringValue(sensorArg._id) === id) || {};
        const data = { ...portConfig, ...sensor };
        ports.push(this.portSnapshot(data, id));
      }
    }

    const configuredIds = new Set(ports.map((portArg) => portArg.id));
    for (const sensor of sensors) {
      const id = stringValue(sensor._id);
      if (!id || configuredIds.has(id)) {
        continue;
      }
      ports.push(this.portSnapshot(sensor, id));
    }

    return ports;
  }

  public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
    return SimpleLocalMapper.toDevices(mfiProfile, snapshotArg);
  }

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

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

  public static isNativeRawData(valueArg: unknown): valueArg is IMfiNativeRawData {
    return Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) && (Array.isArray((valueArg as IMfiNativeRawData).devices) || Array.isArray((valueArg as IMfiNativeRawData).sensors)));
  }

  public static sensorValue(portArg: IMfiPortSnapshot): unknown {
    if (!portArg.tag) {
      return 'off';
    }
    const rawValue = portArg.data[portArg.tag] ?? portArg.data.val;
    if (portArg.model === 'Input Digital') {
      return numberValue(rawValue) && numberValue(rawValue)! > 0 ? 'on' : 'off';
    }
    const numericValue = numberValue(rawValue);
    if (numericValue === undefined) {
      return rawValue ?? null;
    }
    const digits = mfiDigits[portArg.tag] ?? 0;
    return Math.round(numericValue * (10 ** digits)) / (10 ** digits);
  }

  private static portSnapshot(dataArg: Record<string, unknown>, idArg: string): IMfiPortSnapshot {
    const tag = stringValue(dataArg.tag);
    return {
      id: idArg,
      label: stringValue(dataArg.label) || idArg,
      model: stringValue(dataArg.model) || 'mFi Port',
      tag,
      value: tag ? dataArg[tag] ?? dataArg.val : undefined,
      output: dataArg.output ?? dataArg.output_val,
      mac: stringValue(dataArg.mac),
      port: stringValue(dataArg.port),
      data: dataArg,
    };
  }

  private static entitiesFromPorts(portsArg: IMfiPortSnapshot[], onlineArg: boolean, writableArg: boolean): ISimpleLocalSnapshot['entities'] {
    const entities: ISimpleLocalSnapshot['entities'] = [];
    for (const port of portsArg) {
      if (mfiSensorModels.has(port.model)) {
        entities.push({
          id: `sensor_${this.slug(port.id)}`,
          uniqueId: `${mfiProfile.domain}_${this.slug(port.id)}_sensor`,
          name: port.label,
          platform: 'sensor',
          state: this.sensorValue(port),
          available: onlineArg,
          writable: false,
          unit: this.sensorUnit(port),
          deviceClass: port.tag === 'temperature' ? 'temperature' : port.tag === 'active_pwr' ? 'power' : undefined,
          stateClass: port.tag && port.model !== 'Input Digital' ? 'measurement' : undefined,
          attributes: cleanAttributes({
            portId: port.id,
            port: port.port,
            model: port.model,
            tag: port.tag,
            macAddress: port.mac,
          }),
        });
      }
      if (mfiSwitchModels.has(port.model)) {
        entities.push({
          id: `switch_${this.slug(port.id)}`,
          uniqueId: `${mfiProfile.domain}_${this.slug(port.id)}_switch`,
          name: `${port.label} Switch`,
          platform: 'switch',
          state: Boolean(numberValue(port.output) && numberValue(port.output)! > 0),
          available: onlineArg,
          writable: writableArg,
          attributes: cleanAttributes({
            portId: port.id,
            port: port.port,
            model: port.model,
            macAddress: port.mac,
          }),
        });
      }
    }
    return entities;
  }

  private static sensorUnit(portArg: IMfiPortSnapshot): string | undefined {
    if (portArg.tag === 'temperature') {
      return '°C';
    }
    if (portArg.tag === 'active_pwr') {
      return 'Watts';
    }
    if (portArg.model === 'Input Digital') {
      return undefined;
    }
    return portArg.tag;
  }

  private static hasCommandTransport(configArg: IMfiConfig): boolean {
    return this.hasLiveEndpoint(configArg) || Boolean(configArg.client?.execute || configArg.commandExecutor);
  }

  private static hasLiveEndpoint(configArg: IMfiConfig): boolean {
    return Boolean(configArg.host || configArg.url);
  }

  private static endpoint(configArg: IMfiConfig): { host?: string; port?: number; protocol: 'http' | 'https' } {
    const configured = configArg.url || configArg.host;
    const defaultUseTls = configArg.useTls ?? configArg.ssl ?? true;
    if (configured && /^https?:\/\//i.test(configured)) {
      try {
        const url = new URL(configured);
        const protocol = url.protocol === 'http:' ? 'http' : 'https';
        return {
          host: url.hostname,
          port: configArg.port || (url.port ? Number(url.port) : undefined) || (protocol === 'https' ? mfiDefaultHttpsPort : mfiDefaultHttpPort),
          protocol,
        };
      } catch {
        return { host: configured, port: configArg.port || (defaultUseTls ? mfiDefaultHttpsPort : mfiDefaultHttpPort), protocol: defaultUseTls ? 'https' : 'http' };
      }
    }
    return {
      host: configured,
      port: configArg.port || (defaultUseTls ? mfiDefaultHttpsPort : mfiDefaultHttpPort),
      protocol: defaultUseTls ? 'https' : 'http',
    };
  }
}

const stringValue = (valueArg: unknown): string | undefined => {
  if (typeof valueArg === 'string' && valueArg.trim()) {
    return valueArg.trim();
  }
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return String(valueArg);
  }
  return undefined;
};

const numberValue = (valueArg: unknown): number | undefined => {
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return valueArg;
  }
  if (typeof valueArg === 'string' && valueArg.trim()) {
    const parsed = Number(valueArg);
    return Number.isFinite(parsed) ? parsed : undefined;
  }
  return undefined;
};

const cleanAttributes = (attributesArg: Record<string, unknown | undefined>): Record<string, unknown> | undefined => {
  const clean = Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  return Object.keys(clean).length ? clean : undefined;
};
