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 { IFluxLedConfig } from './flux_led.types.js';
import { fluxLedDefaultName, fluxLedDefaultPort, fluxLedProfile } from './flux_led.types.js';

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

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

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

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

  private static normalizeRawData(configArg: IFluxLedConfig, rawDataArg: unknown): unknown {
    if (isRecord(rawDataArg) && 'device' in rawDataArg && 'entities' in rawDataArg) {
      return rawDataArg;
    }
    const rawObject = isRecord(rawDataArg) ? rawDataArg : undefined;
    if (!rawObject || Array.isArray(rawObject.entities)) {
      return rawDataArg;
    }

    const metadata = configArg.metadata || {};
    const host = configArg.host || stringValue(rawObject.ipaddr) || stringValue(rawObject.ip_address) || stringValue(rawObject.host) || stringValue(metadata.host);
    const port = configArg.port || numberValue(rawObject.port) || fluxLedDefaultPort;
    const mac = normalizeMac(stringValue(rawObject.id) || stringValue(rawObject.mac) || stringValue(rawObject.macAddress) || configArg.uniqueId);
    const model = configArg.modelDescription || configArg.model_description || stringValue(rawObject.model_description) || stringValue(rawObject.modelDescription) || configArg.model || stringValue(rawObject.model) || fluxLedProfile.model;
    const shortId = mac ? mac.replace(/:/g, '').slice(-6) : undefined;
    const name = configArg.name || stringValue(rawObject.name) || (shortId ? `${model} ${shortId}` : fluxLedDefaultName);
    const uniqueBase = SimpleLocalMapper.slug(mac || host || name);
    const entities = fluxEntities(rawObject, configArg, uniqueBase);

    return {
      device: {
        id: configArg.uniqueId || mac || host || name,
        name,
        manufacturer: fluxLedProfile.manufacturer,
        model,
        serialNumber: mac,
        host,
        port,
        protocol: fluxLedProfile.defaultProtocol,
        configurationUrl: host ? `http://${host}` : undefined,
        attributes: {
          modelNum: configArg.modelNum ?? configArg.model_num ?? rawObject.model_num ?? rawObject.modelNum,
          modelInfo: configArg.modelInfo ?? configArg.model_info ?? rawObject.model_info ?? rawObject.modelInfo,
          minorVersion: configArg.minorVersion ?? configArg.minor_version ?? rawObject.version_num ?? rawObject.minorVersion,
          remoteAccessEnabled: configArg.remoteAccessEnabled ?? configArg.remote_access_enabled ?? rawObject.remote_access_enabled ?? rawObject.remoteAccessEnabled,
          whiteChannelType: configArg.whiteChannelType ?? configArg.white_channel_type ?? rawObject.white_channel_type ?? rawObject.whiteChannelType,
        },
      },
      entities: entities.length ? entities : [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: true, writable: false }],
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const fluxEntities = (rawObjectArg: Record<string, unknown>, configArg: IFluxLedConfig, uniqueBaseArg: string): ISimpleLocalEntitySnapshot[] => {
  const entities: ISimpleLocalEntitySnapshot[] = [];
  const deviceType = stringValue(rawObjectArg.device_type) || stringValue(rawObjectArg.deviceType) || 'Bulb';
  const isSwitchDevice = deviceType.toLowerCase() === 'switch';
  const isOn = booleanValue(rawObjectArg.is_on ?? rawObjectArg.isOn ?? rawObjectArg.power ?? rawObjectArg.on, false);

  if (!isSwitchDevice || rawObjectArg.brightness !== undefined || rawObjectArg.rgb !== undefined || rawObjectArg.effect !== undefined) {
    entities.push({
      id: 'light',
      uniqueId: `${fluxLedProfile.domain}_${uniqueBaseArg}_light`,
      name: 'Light',
      platform: 'light',
      state: isOn,
      writable: true,
      attributes: {
        brightness: rawObjectArg.brightness,
        colorMode: rawObjectArg.color_mode ?? rawObjectArg.colorMode,
        colorModes: rawObjectArg.color_modes ?? rawObjectArg.colorModes,
        colorTempKelvin: rawObjectArg.color_temp ?? rawObjectArg.colorTempKelvin,
        rgbColor: rawObjectArg.rgb ?? rawObjectArg.rgb_color,
        rgbwColor: rawObjectArg.rgbw ?? rawObjectArg.rgbw_color,
        rgbwwColor: rawObjectArg.rgbcw ?? rawObjectArg.rgbww ?? rawObjectArg.rgbww_color,
        effect: rawObjectArg.effect,
        effectList: rawObjectArg.effect_list ?? rawObjectArg.effectList,
      },
    });
  }

  if (isSwitchDevice) {
    entities.push({ id: 'switch', uniqueId: `${fluxLedProfile.domain}_${uniqueBaseArg}_switch`, name: 'Switch', platform: 'switch', state: isOn, writable: true });
  }
  if (rawObjectArg.remote_access_host ?? rawObjectArg.remoteAccessHost ?? configArg.remoteAccessHost ?? configArg.remote_access_host) {
    entities.push({
      id: 'remote_access',
      uniqueId: `${fluxLedProfile.domain}_${uniqueBaseArg}_remote_access`,
      name: 'Remote access',
      platform: 'switch',
      state: booleanValue(configArg.remoteAccessEnabled ?? configArg.remote_access_enabled ?? rawObjectArg.remote_access_enabled ?? rawObjectArg.remoteAccessEnabled, false),
      writable: true,
      attributes: { entityCategory: 'config' },
    });
  }
  if (truthy(rawObjectArg.microphone)) {
    entities.push({ id: 'music', uniqueId: `${fluxLedProfile.domain}_${uniqueBaseArg}_music`, name: 'Music', platform: 'switch', state: stringValue(rawObjectArg.effect)?.toLowerCase() === 'music', writable: true });
  }

  addNumber(entities, rawObjectArg, uniqueBaseArg, 'effect_speed', 'Effect speed', ['speed'], { min: 1, max: 100 });
  addNumber(entities, rawObjectArg, uniqueBaseArg, 'pixels_per_segment', 'Pixels per segment', ['pixels_per_segment', 'pixelsPerSegment'], { entityCategory: 'config' });
  addNumber(entities, rawObjectArg, uniqueBaseArg, 'segments', 'Segments', ['segments'], { entityCategory: 'config' });
  addNumber(entities, rawObjectArg, uniqueBaseArg, 'music_pixels_per_segment', 'Music pixels per segment', ['music_pixels_per_segment', 'musicPixelsPerSegment'], { entityCategory: 'config' });
  addNumber(entities, rawObjectArg, uniqueBaseArg, 'music_segments', 'Music segments', ['music_segments', 'musicSegments'], { entityCategory: 'config' });

  addSelect(entities, rawObjectArg, uniqueBaseArg, 'operating_mode', 'Operating mode', ['operating_mode', 'operatingMode'], ['operating_modes', 'operatingModes']);
  addSelect(entities, rawObjectArg, uniqueBaseArg, 'wiring', 'Wiring', ['wiring'], ['wirings']);
  addSelect(entities, rawObjectArg, uniqueBaseArg, 'ic_type', 'IC type', ['ic_type', 'icType'], ['ic_types', 'icTypes']);
  addSelect(entities, rawObjectArg, uniqueBaseArg, 'remote_config', 'Remote config', ['remote_config', 'remoteConfig'], ['remote_configs', 'remoteConfigs']);
  addSelect(entities, rawObjectArg, uniqueBaseArg, 'white_channel', 'White channel', ['white_channel_type', 'whiteChannelType'], ['white_channel_types', 'whiteChannelTypes']);

  if (rawObjectArg.paired_remotes !== undefined || rawObjectArg.pairedRemotes !== undefined) {
    const pairedRemotes = rawObjectArg.paired_remotes ?? rawObjectArg.pairedRemotes;
    entities.push({ id: 'paired_remotes', uniqueId: `${fluxLedProfile.domain}_${uniqueBaseArg}_paired_remotes`, name: 'Paired remotes', platform: 'sensor', state: pairedRemotes, writable: false, attributes: { entityCategory: 'diagnostic' } });
    entities.push({ id: 'unpair_remotes', uniqueId: `${fluxLedProfile.domain}_${uniqueBaseArg}_unpair_remotes`, name: 'Unpair remotes', platform: 'button', state: 'available', writable: true, attributes: { entityCategory: 'config' } });
  }
  entities.push({ id: 'restart', uniqueId: `${fluxLedProfile.domain}_${uniqueBaseArg}_restart`, name: 'Restart', platform: 'button', state: 'available', writable: true, attributes: { entityCategory: 'config', deviceClass: 'restart' } });
  return entities;
};

const addNumber = (entitiesArg: ISimpleLocalEntitySnapshot[], rawObjectArg: Record<string, unknown>, uniqueBaseArg: string, idArg: string, nameArg: string, keysArg: string[], attributesArg?: Record<string, unknown>) => {
  const state = rawValue(rawObjectArg, keysArg);
  if (state === undefined) {
    return;
  }
  entitiesArg.push({ id: idArg, uniqueId: `${fluxLedProfile.domain}_${uniqueBaseArg}_${idArg}`, name: nameArg, platform: 'number', state, writable: true, attributes: attributesArg });
};

const addSelect = (entitiesArg: ISimpleLocalEntitySnapshot[], rawObjectArg: Record<string, unknown>, uniqueBaseArg: string, idArg: string, nameArg: string, valueKeysArg: string[], optionKeysArg: string[]) => {
  const state = rawValue(rawObjectArg, valueKeysArg);
  const options = rawValue(rawObjectArg, optionKeysArg);
  if (state === undefined && !Array.isArray(options)) {
    return;
  }
  entitiesArg.push({ id: idArg, uniqueId: `${fluxLedProfile.domain}_${uniqueBaseArg}_${idArg}`, name: nameArg, platform: 'select', state: state ?? null, writable: true, attributes: { options, entityCategory: 'config' } });
};

const rawValue = (rawObjectArg: Record<string, unknown>, keysArg: string[]): unknown => {
  for (const key of keysArg) {
    if (rawObjectArg[key] !== undefined) {
      return rawObjectArg[key];
    }
  }
  return undefined;
};

const normalizeMac = (valueArg: string | undefined): string | undefined => {
  const compact = valueArg?.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
  if (!compact || compact.length !== 12) {
    return valueArg;
  }
  return compact.match(/../g)?.join(':');
};

const booleanValue = (valueArg: unknown, defaultArg: boolean): boolean => typeof valueArg === 'boolean' ? valueArg : valueArg === undefined ? defaultArg : valueArg === 'true' || valueArg === 1 || valueArg === 'on';

const truthy = (valueArg: unknown): boolean => booleanValue(valueArg, false);

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) ? Math.round(value) : 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;
