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 { IFlicConfig, TFlicClickType } from './flic.types.js';
import { flicDefaultHost, flicDefaultPort, flicProfile } from './flic.types.js';

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

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

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

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

  private static normalizeRawData(configArg: IFlicConfig, 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 host = configArg.host || stringValue(rawObject.host) || flicDefaultHost;
    const port = configArg.port || numberValue(rawObject.port) || flicDefaultPort;
    const buttons = buttonRecords(rawObject);
    const entities = buttons.map((buttonArg) => buttonEntity(buttonArg, configArg));

    return {
      device: {
        id: configArg.uniqueId || `${host}:${port}`,
        name: configArg.name || stringValue(rawObject.name) || flicProfile.defaultName,
        manufacturer: flicProfile.manufacturer,
        model: flicProfile.model,
        host,
        port,
        protocol: flicProfile.defaultProtocol,
        attributes: {
          discovery: configArg.discovery ?? rawObject.discovery,
          timeout: configArg.timeout ?? rawObject.timeout,
          eventName: 'flic_click',
        },
      },
      entities: entities.length ? entities : [{ id: 'server', name: 'Flic server', platform: 'binary_sensor', state: true, writable: false }],
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const buttonEntity = (buttonArg: Record<string, unknown>, configArg: IFlicConfig): ISimpleLocalEntitySnapshot => {
  const address = stringValue(buttonArg.address) || stringValue(buttonArg.button_address) || stringValue(buttonArg.bd_addr) || stringValue(buttonArg.id) || 'button';
  const compactAddress = address.replace(/:/g, '').toLowerCase();
  const name = stringValue(buttonArg.name) || stringValue(buttonArg.button_name) || `flic_${compactAddress}`;
  const ignoredClickTypes = configArg.ignoredClickTypes || configArg.ignored_click_types || clickTypes(buttonArg.ignoredClickTypes) || clickTypes(buttonArg.ignored_click_types) || [];
  return {
    id: `button_${SimpleLocalMapper.slug(address)}`,
    uniqueId: `${flicProfile.domain}_${SimpleLocalMapper.slug(address)}`,
    name,
    platform: 'binary_sensor',
    state: booleanValue(buttonArg.is_on ?? buttonArg.isOn ?? buttonArg.connected, true),
    writable: false,
    attributes: {
      address,
      clickType: buttonArg.click_type ?? buttonArg.clickType,
      queuedTime: buttonArg.queued_time ?? buttonArg.queuedTime,
      ignoredClickTypes,
      eventName: 'flic_click',
      eventDataKeys: ['button_name', 'button_address', 'click_type', 'queued_time'],
    },
  };
};

const buttonRecords = (rawObjectArg: Record<string, unknown>): Record<string, unknown>[] => {
  if (Array.isArray(rawObjectArg.buttons)) {
    return rawObjectArg.buttons.filter(isRecord);
  }
  if (Array.isArray(rawObjectArg.verified_buttons)) {
    return rawObjectArg.verified_buttons.filter(isRecord);
  }
  if (rawObjectArg.address || rawObjectArg.button_address || rawObjectArg.bd_addr) {
    return [rawObjectArg];
  }
  return [];
};

const clickTypes = (valueArg: unknown): TFlicClickType[] | undefined => Array.isArray(valueArg) ? valueArg.filter((itemArg): itemArg is TFlicClickType => itemArg === 'single' || itemArg === 'double' || itemArg === 'hold') : undefined;

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

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;
