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 { IGoveeLightLocalConfig } from './govee_light_local.types.js';
import { goveeLightLocalDefaultListeningPort, goveeLightLocalDefaultName, goveeLightLocalDefaultTargetPort, goveeLightLocalProfile } from './govee_light_local.types.js';

export class GoveeLightLocalMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IGoveeLightLocalConfig>, 'profile'>): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({
      ...optionsArg,
      profile: goveeLightLocalProfile,
      rawData: this.normalizeRawData(optionsArg.config, optionsArg.rawData),
    });
  }

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

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

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

  private static normalizeRawData(configArg: IGoveeLightLocalConfig, rawDataArg: unknown): unknown {
    if (!isRecord(rawDataArg) || ('device' in rawDataArg && 'entities' in rawDataArg)) {
      return rawDataArg;
    }

    const devices = lightDevices(rawDataArg);
    if (!devices.length) {
      return rawDataArg;
    }

    const firstDevice = devices[0];
    const firstFingerprint = fingerprintValue(firstDevice) || configArg.uniqueId;
    const firstSku = stringValue(firstDevice.sku) || stringValue(firstDevice.model);
    const name = configArg.name || stringValue(firstDevice.name) || firstSku || goveeLightLocalDefaultName;
    const entities = devices.map((deviceArg) => lightEntity(configArg, deviceArg));

    return {
      device: {
        id: configArg.uniqueId || firstFingerprint || name,
        name,
        manufacturer: goveeLightLocalProfile.manufacturer,
        model: firstSku || goveeLightLocalProfile.model,
        serialNumber: firstFingerprint,
        host: configArg.host || stringValue(firstDevice.ip) || stringValue(firstDevice.host),
        port: configArg.targetPort || configArg.port || goveeLightLocalDefaultTargetPort,
        protocol: 'local',
        attributes: {
          sourceIp: configArg.sourceIp,
          broadcastAddress: configArg.broadcastAddress,
          listeningPort: configArg.listeningPort || goveeLightLocalDefaultListeningPort,
          deviceCount: devices.length,
        },
      },
      entities,
      online: configArg.online ?? entities.some((entityArg) => entityArg.available !== false),
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const lightDevices = (rawDataArg: Record<string, unknown>): Array<Record<string, unknown>> => {
  const direct = recordsFrom(rawDataArg.devices ?? rawDataArg.lights ?? rawDataArg.deviceList ?? rawDataArg.device_list);
  if (direct.length) {
    return direct;
  }
  return hasLightHints(rawDataArg) ? [rawDataArg] : [];
};

const lightEntity = (configArg: IGoveeLightLocalConfig, deviceArg: Record<string, unknown>): ISimpleLocalEntitySnapshot => {
  const fingerprint = fingerprintValue(deviceArg) || stringValue(deviceArg.id) || stringValue(deviceArg.name) || configArg.uniqueId || goveeLightLocalDefaultName;
  const sku = stringValue(deviceArg.sku) || stringValue(deviceArg.model);
  const name = stringValue(deviceArg.name) || sku || configArg.name || 'Light';
  const brightnessPercent = numberValue(deviceArg.brightness ?? deviceArg.brightnessPercent ?? deviceArg.brightness_percent);
  const rgb = tupleNumber3(deviceArg.rgb_color) || tupleNumber3(deviceArg.rgbColor) || tupleNumber3(deviceArg.color);
  const colorTempKelvin = numberValue(deviceArg.temperature_color ?? deviceArg.temperatureColor ?? deviceArg.colorTempKelvin ?? deviceArg.color_temp_kelvin);
  const scenes = sceneNames(deviceArg);

  return {
    id: `light_${SimpleLocalMapper.slug(fingerprint)}`,
    uniqueId: `${goveeLightLocalProfile.domain}_${SimpleLocalMapper.slug(fingerprint)}_light`,
    name,
    platform: 'light',
    state: booleanValue(deviceArg.on ?? deviceArg.power ?? deviceArg.is_on) ?? false,
    available: booleanValue(deviceArg.available) ?? true,
    writable: true,
    attributes: {
      sku,
      fingerprint,
      brightness: brightnessPercent !== undefined ? Math.round((Math.max(0, Math.min(100, brightnessPercent)) / 100) * 255) : undefined,
      brightnessPercent,
      colorTempKelvin,
      rgbColor: rgb,
      effectList: scenes.length ? ['none', ...scenes] : undefined,
      supportedColorModes: supportedColorModes(deviceArg, Boolean(rgb), colorTempKelvin !== undefined, brightnessPercent !== undefined),
      lastSeen: stringValue(deviceArg.lastseen) || stringValue(deviceArg.lastSeen),
      serviceDomain: 'light',
    },
  };
};

const hasLightHints = (valueArg: Record<string, unknown>): boolean => Boolean(fingerprintValue(valueArg) || stringValue(valueArg.sku) || stringValue(valueArg.model) || 'on' in valueArg || 'brightness' in valueArg || 'rgb_color' in valueArg || 'temperature_color' in valueArg);

const fingerprintValue = (valueArg: Record<string, unknown>): string | undefined => stringValue(valueArg.fingerprint) || stringValue(valueArg.serialNumber) || stringValue(valueArg.serial_number) || stringValue(valueArg.mac) || stringValue(valueArg.deviceId) || stringValue(valueArg.device_id);

const sceneNames = (valueArg: Record<string, unknown>): string[] => {
  const capabilities = recordValue(valueArg.capabilities);
  const scenes = recordValue(capabilities?.scenes) || recordValue(valueArg.scenes);
  return scenes ? Object.keys(scenes).filter(Boolean) : [];
};

const supportedColorModes = (deviceArg: Record<string, unknown>, hasRgbArg: boolean, hasColorTempArg: boolean, hasBrightnessArg: boolean): string[] => {
  const modes = new Set<string>(['onoff']);
  const capabilities = recordValue(deviceArg.capabilities);
  const features = stringValue(capabilities?.features) || stringValue(deviceArg.features);
  if (hasRgbArg || features?.toLowerCase().includes('rgb')) modes.add('rgb');
  if (hasColorTempArg || features?.toLowerCase().includes('kelvin') || features?.toLowerCase().includes('temperature')) modes.add('color_temp');
  if (hasBrightnessArg || features?.toLowerCase().includes('brightness')) modes.add('brightness');
  return [...modes];
};

const recordsFrom = (valueArg: unknown): Array<Record<string, unknown>> => {
  if (Array.isArray(valueArg)) {
    return valueArg.filter(isRecord);
  }
  if (isRecord(valueArg)) {
    return Object.values(valueArg).filter(isRecord);
  }
  return [];
};

const tupleNumber3 = (valueArg: unknown): [number, number, number] | undefined => {
  if (!Array.isArray(valueArg) || valueArg.length < 3) {
    return undefined;
  }
  const values = valueArg.slice(0, 3).map(numberValue);
  return values.every((value) => value !== undefined) ? values as [number, number, number] : undefined;
};

const recordValue = (valueArg: unknown): Record<string, unknown> | undefined => isRecord(valueArg) ? valueArg : 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;
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) ? value : undefined;
};
const booleanValue = (valueArg: unknown): boolean | undefined => {
  if (typeof valueArg === 'boolean') return valueArg;
  if (typeof valueArg === 'number') return valueArg !== 0;
  const value = stringValue(valueArg)?.toLowerCase();
  if (!value) return undefined;
  if (['1', 'true', 'yes', 'on', 'enabled'].includes(value)) return true;
  if (['0', 'false', 'no', 'off', 'disabled'].includes(value)) return false;
  return undefined;
};
