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 { ILunatoneConfig, ILunatoneDeviceData, ILunatoneInfoData, ILunatoneRawData } from './lunatone.types.js';
import { lunatoneDefaultName, lunatoneProfile } from './lunatone.types.js';

const articleNumberNameMapping: Record<number, string> = {
  86456840: "DALI-2 Display 7''",
  86456841: "DALI-2 Display 4''",
  89453886: 'DALI-2 IoT',
  22176625: 'DALI-2 IoT4',
};

export class LunatoneMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<ILunatoneConfig>, 'profile'>): ISimpleLocalSnapshot {
    if (isLunatoneRawData(optionsArg.rawData)) {
      return this.toSnapshotFromApi(configWithRaw(optionsArg.config, optionsArg.rawData), optionsArg.rawData, optionsArg.online ?? true, optionsArg.source || 'http', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: lunatoneProfile });
  }

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

  public static toSnapshotFromApi(configArg: ILunatoneConfig, rawDataArg: ILunatoneRawData, onlineArg = true, sourceArg: ISimpleLocalSnapshot['source'] = 'http', errorArg?: string): ISimpleLocalSnapshot {
    const info = rawDataArg.info;
    const devices = devicesFromRaw(rawDataArg.devices);
    const host = this.host(configArg);
    const serial = info?.device?.serial;
    const model = productName(info) || lunatoneProfile.model;
    const entities: ISimpleLocalEntitySnapshot[] = [
      {
        id: 'online',
        name: 'Online',
        platform: 'binary_sensor',
        state: onlineArg,
        available: true,
        writable: false,
        attributes: { deviceClass: 'connectivity' },
      },
      {
        id: 'devices',
        name: 'Devices',
        platform: 'sensor',
        state: devices.length,
        available: onlineArg,
        writable: false,
        attributes: { stateClass: 'measurement' },
      },
    ];

    for (const [line, lineData] of Object.entries(info?.lines || {})) {
      const lineStatus = stringValue(lineData?.lineStatus) || 'unknown';
      entities.push({
        id: `line_${this.slug(line)}`,
        name: `DALI Line ${line}`,
        platform: 'light',
        state: lineStatus === 'ok',
        available: onlineArg && lineStatus === 'ok',
        writable: true,
        attributes: cleanAttributes({
          line: Number(line),
          lineStatus,
          serialNumber: lineData?.device?.serial,
          articleNumber: lineData?.device?.articleNumber,
          pcb: lineData?.device?.pcb,
          assumedState: true,
        }),
      });
    }

    for (const device of devices) {
      entities.push({
        id: `device_${device.id}`,
        name: device.name || `DALI Device ${device.id}`,
        platform: 'light',
        state: isDeviceOn(device),
        available: onlineArg && device.available !== false,
        writable: true,
        attributes: cleanAttributes({
          address: device.address,
          line: device.line,
          type: device.type,
          brightness: brightnessTo255(featureStatusNumber(device, 'dimmable')),
          nativeBrightness: featureStatusNumber(device, 'dimmable'),
          colorTempKelvin: featureStatusNumber(device, 'colorKelvin'),
          rgbColor: rgbTuple(device),
          rgbwColor: rgbwTuple(device),
          groups: device.groups,
          daliTypes: device.daliTypes,
          status: device.status,
        }),
      });
    }

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || stringValue(info?.uid) || (serial !== undefined ? String(serial) : undefined) || host || lunatoneDefaultName,
        name: configArg.name || info?.name || lunatoneDefaultName,
        manufacturer: 'Lunatone Industrielle Elektronik GmbH',
        model,
        serialNumber: serial !== undefined ? String(serial) : undefined,
        host,
        port: configArg.port || (host ? 80 : undefined),
        protocol: configArg.useTls ? 'https' : 'http',
        configurationUrl: baseUrl(configArg),
        attributes: cleanAttributes({
          uid: info?.uid,
          version: info?.version,
          tier: info?.tier,
          articleNumber: info?.device?.articleNumber,
          articleInfo: info?.device?.articleInfo,
          lines: Object.keys(info?.lines || {}).length,
        }),
      },
      entities,
      online: onlineArg,
      updatedAt: new Date().toISOString(),
      source: sourceArg,
      rawData: rawDataArg,
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: true,
      },
    };
  }

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

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

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

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

export const lunatoneControlBodyForService = (serviceArg: string, dataArg: Record<string, unknown> | undefined, currentDeviceArg?: ILunatoneDeviceData): Record<string, unknown> => {
  if (serviceArg === 'turn_off') {
    return featureStatus(currentDeviceArg, 'dimmable') !== undefined || !currentDeviceArg ? { dimmable: 0 } : { switchable: false };
  }
  const body: Record<string, unknown> = {};
  const brightness = numberValue(dataArg?.brightness ?? dataArg?.level ?? dataArg?.brightness_pct);
  if (brightness !== undefined) {
    body.dimmable = brightness > 100 ? Math.round((brightness / 255) * 100) : brightness;
  }
  const colorTemp = numberValue(dataArg?.color_temp_kelvin ?? dataArg?.colorTempKelvin ?? dataArg?.kelvin);
  if (colorTemp !== undefined) {
    body.colorKelvin = colorTemp;
  }
  const rgb = Array.isArray(dataArg?.rgb_color) ? dataArg?.rgb_color : Array.isArray(dataArg?.rgbColor) ? dataArg?.rgbColor : undefined;
  if (rgb && rgb.length >= 3) {
    body.colorRGB = { r: Number(rgb[0]) / 255, g: Number(rgb[1]) / 255, b: Number(rgb[2]) / 255 };
  }
  const rgbw = Array.isArray(dataArg?.rgbw_color) ? dataArg?.rgbw_color : Array.isArray(dataArg?.rgbwColor) ? dataArg?.rgbwColor : undefined;
  if (rgbw && rgbw.length >= 4) {
    body.colorRGB = { r: Number(rgbw[0]) / 255, g: Number(rgbw[1]) / 255, b: Number(rgbw[2]) / 255 };
    body.colorWAF = { w: Number(rgbw[3]) / 255 };
  }
  if (!Object.keys(body).length) {
    body.switchable = true;
  }
  return body;
};

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

const configWithRaw = (configArg: ILunatoneConfig, rawDataArg: ILunatoneRawData): ILunatoneConfig => {
  return { ...configArg, rawData: rawDataArg };
};

const devicesFromRaw = (devicesArg: ILunatoneRawData['devices']): ILunatoneDeviceData[] => {
  if (Array.isArray(devicesArg)) {
    return devicesArg.filter((deviceArg): deviceArg is ILunatoneDeviceData => typeof deviceArg?.id === 'number');
  }
  if (devicesArg && Array.isArray(devicesArg.devices)) {
    return devicesArg.devices.filter((deviceArg): deviceArg is ILunatoneDeviceData => typeof deviceArg?.id === 'number');
  }
  return [];
};

const productName = (infoArg: ILunatoneInfoData | undefined): string | undefined => {
  const articleNumber = infoArg?.device?.articleNumber;
  const baseName = articleNumber !== undefined ? articleNumberNameMapping[articleNumber] : undefined;
  return baseName;
};

const isDeviceOn = (deviceArg: ILunatoneDeviceData): boolean => {
  const switchable = featureStatus(deviceArg, 'switchable');
  if (typeof switchable === 'boolean') {
    return switchable;
  }
  const brightness = featureStatusNumber(deviceArg, 'dimmable');
  return brightness !== undefined ? brightness > 0 : deviceArg.available !== false;
};

const featureStatusNumber = (deviceArg: ILunatoneDeviceData, featureArg: string): number | undefined => {
  const value = featureStatus(deviceArg, featureArg);
  return numberValue(value);
};

const featureStatus = (deviceArg: ILunatoneDeviceData | undefined, featureArg: string): unknown => {
  if (!deviceArg?.features) {
    return undefined;
  }
  const value = deviceArg.features[featureArg];
  if (value && typeof value === 'object' && !Array.isArray(value) && 'status' in value) {
    return (value as { status?: unknown }).status;
  }
  return value;
};

const rgbTuple = (deviceArg: ILunatoneDeviceData): [number, number, number] | undefined => {
  const color = featureStatus(deviceArg, 'colorRGB');
  if (!color || typeof color !== 'object' || Array.isArray(color)) {
    return undefined;
  }
  const record = color as Record<string, unknown>;
  return [toColor255(record.r ?? record.red), toColor255(record.g ?? record.green), toColor255(record.b ?? record.blue)];
};

const rgbwTuple = (deviceArg: ILunatoneDeviceData): [number, number, number, number] | undefined => {
  const rgb = rgbTuple(deviceArg);
  const waf = featureStatus(deviceArg, 'colorWAF');
  if (!rgb || !waf || typeof waf !== 'object' || Array.isArray(waf)) {
    return undefined;
  }
  const record = waf as Record<string, unknown>;
  return [...rgb, toColor255(record.w ?? record.white)];
};

const brightnessTo255 = (valueArg: number | undefined): number | undefined => valueArg === undefined ? undefined : Math.round((valueArg / 100) * 255);

const toColor255 = (valueArg: unknown): number => {
  const value = numberValue(valueArg) || 0;
  return value <= 1 ? Math.round(value * 255) : Math.round(value);
};

const baseUrl = (configArg: ILunatoneConfig): string | undefined => {
  if (configArg.url) {
    return configArg.url.replace(/\/+$/, '');
  }
  if (!configArg.host) {
    return undefined;
  }
  const portPart = configArg.port && !configArg.host.includes(':') && configArg.port !== 80 ? `:${configArg.port}` : '';
  return `${configArg.useTls ? 'https' : 'http'}://${configArg.host}${portPart}`;
};

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

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