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 { ILuciConfig, ILuciConnectedDevice, ILuciRawData } from './luci.types.js';
import { luciDefaultName, luciProfile } from './luci.types.js';

export class LuciMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<ILuciConfig>, 'profile'>): ISimpleLocalSnapshot {
    if (isLuciRawData(optionsArg.rawData)) {
      return this.toSnapshotFromApi(optionsArg.config, optionsArg.rawData, optionsArg.online ?? true, optionsArg.source || 'http', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: luciProfile });
  }

  public static toSnapshotFromRaw(configArg: ILuciConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
    if (isLuciRawData(rawDataArg)) {
      return this.toSnapshotFromApi(configArg, rawDataArg, true, 'manual');
    }
    return SimpleLocalMapper.toSnapshot({ profile: luciProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
  }

  public static toSnapshotFromApi(configArg: ILuciConfig, rawDataArg: ILuciRawData, onlineArg = true, sourceArg: ISimpleLocalSnapshot['source'] = 'http', errorArg?: string): ISimpleLocalSnapshot {
    const host = stringValue(rawDataArg.router?.host) || this.host(configArg);
    const version = stringValue(rawDataArg.router?.version);
    const name = configArg.name || luciDefaultName;
    const devices = this.connectedDevices(rawDataArg.devices || []);
    const entities: ISimpleLocalEntitySnapshot[] = [
      {
        id: 'online',
        name: 'Online',
        platform: 'binary_sensor',
        state: onlineArg,
        available: true,
        writable: false,
        attributes: { deviceClass: 'connectivity' },
      },
      {
        id: 'connected_devices',
        name: 'Connected Devices',
        platform: 'sensor',
        state: devices.length,
        available: onlineArg,
        writable: false,
        attributes: { stateClass: 'measurement' },
      },
    ];

    for (const device of devices) {
      const mac = this.normalizeMac(device.mac) || this.slug(device.mac || device.ip || device.hostname);
      entities.push({
        id: `device_${this.slug(mac)}`,
        name: `${device.hostname || device.ip || mac} Connected`,
        platform: 'binary_sensor',
        state: device.reachable !== false,
        available: onlineArg,
        writable: false,
        attributes: cleanAttributes({
          deviceClass: 'connectivity',
          macAddress: this.normalizeMac(device.mac),
          ipAddress: device.ip,
          hostname: device.hostname || undefined,
          interface: device.dev,
          routerHost: device.host || host,
          reachable: device.reachable,
          source: 'luci_rpc',
          ...device.raw,
        }),
      });
    }

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || host || name,
        name,
        manufacturer: 'OpenWrt',
        model: version ? `OpenWrt ${version} LuCI` : 'OpenWrt LuCI router',
        host,
        port: configArg.port || (host ? 80 : undefined),
        protocol: configArg.ssl || configArg.useSsl || configArg.useTls ? 'https' : 'http',
        configurationUrl: host ? `${configArg.ssl || configArg.useSsl || configArg.useTls ? 'https' : 'http'}://${host}${configArg.port && configArg.port !== 80 ? `:${configArg.port}` : ''}/` : undefined,
        attributes: cleanAttributes({ version, legacy: rawDataArg.router?.legacy, authenticated: rawDataArg.router?.authenticated }),
      },
      entities,
      online: onlineArg,
      updatedAt: new Date().toISOString(),
      source: sourceArg,
      rawData: rawDataArg,
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: false,
      },
    };
  }

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

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

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

  public static normalizeMac(valueArg: unknown): string | undefined {
    if (typeof valueArg !== 'string') {
      return undefined;
    }
    const compact = valueArg.replace(/[^0-9a-f]/gi, '').toLowerCase();
    return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : undefined;
  }

  private static connectedDevices(devicesArg: ILuciConnectedDevice[]): ILuciConnectedDevice[] {
    const seen = new Set<string>();
    const devices: ILuciConnectedDevice[] = [];
    for (const device of devicesArg) {
      const key = this.normalizeMac(device.mac) || stringValue(device.ip) || stringValue(device.hostname);
      if (!key || seen.has(key)) {
        continue;
      }
      seen.add(key);
      devices.push({ ...device, mac: this.normalizeMac(device.mac) || device.mac });
    }
    return devices;
  }

  private static host(configArg: ILuciConfig): string | undefined {
    if (configArg.host) {
      return configArg.host;
    }
    if (!configArg.url) {
      return undefined;
    }
    try {
      return new URL(configArg.url).host;
    } catch {
      return undefined;
    }
  }
}

const isLuciRawData = (valueArg: unknown): valueArg is ILuciRawData => {
  return Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) && ('devices' in valueArg || 'router' in valueArg));
};

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

const 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;
};
