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 { ILoqedConfig, ILoqedStatusData } from './loqed.types.js';
import { loqedDefaultName, loqedProfile } from './loqed.types.js';

export class LoqedMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<ILoqedConfig>, 'profile'>): ISimpleLocalSnapshot {
    if (isLoqedStatusData(optionsArg.rawData)) {
      return this.toSnapshotFromStatus(optionsArg.config, optionsArg.rawData, optionsArg.online ?? true, optionsArg.source || 'http', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: loqedProfile });
  }

  public static toSnapshotFromRaw(configArg: ILoqedConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
    if (isLoqedStatusData(rawDataArg)) {
      return this.toSnapshotFromStatus(configArg, rawDataArg, true, 'manual');
    }
    return SimpleLocalMapper.toSnapshot({ profile: loqedProfile, config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
  }

  public static toSnapshotFromStatus(configArg: ILoqedConfig, statusArg: ILoqedStatusData | undefined, onlineArg = true, sourceArg: ISimpleLocalSnapshot['source'] = 'http', errorArg?: string): ISimpleLocalSnapshot {
    const host = this.host(configArg, statusArg);
    const boltState = stringValue(statusArg?.bolt_state) || 'unknown';
    const lockOnline = boolValue(statusArg?.lock_online, onlineArg);
    const entities: ISimpleLocalEntitySnapshot[] = [
      {
        id: 'lock',
        name: 'Lock',
        platform: 'switch',
        state: isLocked(boltState),
        available: onlineArg && lockOnline,
        writable: true,
        attributes: { boltState, nativePlatform: 'lock' },
      },
      {
        id: 'bolt_state',
        name: 'Bolt State',
        platform: 'sensor',
        state: boltState,
        available: onlineArg,
        writable: false,
      },
      {
        id: 'lock_online',
        name: 'Lock Online',
        platform: 'binary_sensor',
        state: lockOnline,
        available: onlineArg,
        writable: false,
        attributes: { deviceClass: 'connectivity' },
      },
    ];

    pushNumericEntity(entities, 'battery', 'Battery', statusArg?.battery_percentage, '%', 'battery');
    pushNumericEntity(entities, 'battery_voltage', 'Battery Voltage', statusArg?.battery_voltage, 'V', 'voltage');
    pushNumericEntity(entities, 'wifi_strength', 'WiFi Strength', statusArg?.wifi_strength, undefined, 'signal_strength');
    pushNumericEntity(entities, 'ble_strength', 'BLE Strength', statusArg?.ble_strength, undefined, 'signal_strength');

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || stringValue(statusArg?.bridge_mac_wifi) || host || loqedDefaultName,
        name: configArg.name || loqedDefaultName,
        manufacturer: 'LOQED',
        model: 'LOQED Touch Smart Lock bridge',
        serialNumber: stringValue(statusArg?.bridge_mac_wifi),
        host,
        port: configArg.port || (host ? 80 : undefined),
        protocol: configArg.useTls ? 'https' : 'http',
        configurationUrl: baseUrl(configArg),
        attributes: cleanAttributes({
          bridgeMacWifi: statusArg?.bridge_mac_wifi,
          bridgeMacBle: statusArg?.bridge_mac_ble,
          ipAddress: statusArg?.ip_address,
          batteryType: statusArg?.battery_type,
          batteryTypeNumeric: statusArg?.battery_type_numeric,
          boltStateNumeric: statusArg?.bolt_state_numeric,
          webhooksNumber: statusArg?.webhooks_number,
          upTimestamp: statusArg?.up_timestamp,
        }),
      },
      entities,
      online: onlineArg,
      updatedAt: new Date().toISOString(),
      source: sourceArg,
      rawData: statusArg,
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: true,
        liveControlRequiresLocalKey: true,
      },
    };
  }

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

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

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

  private static host(configArg: ILoqedConfig, statusArg?: ILoqedStatusData): string | undefined {
    return configArg.host || configArg.bridgeIp || configArg.bridge_ip || stringValue(statusArg?.ip_address) || hostFromUrl(configArg.url);
  }
}

export const loqedActionForService = (serviceArg: string, boltStateArg?: string): 'open' | 'unlock' | 'lock' | undefined => {
  if (serviceArg === 'open') {
    return 'open';
  }
  if (serviceArg === 'unlock' || serviceArg === 'turn_off') {
    return 'unlock';
  }
  if (serviceArg === 'lock' || serviceArg === 'turn_on') {
    return 'lock';
  }
  if (serviceArg === 'toggle') {
    return isLocked(boltStateArg || '') ? 'unlock' : 'lock';
  }
  return undefined;
};

const isLoqedStatusData = (valueArg: unknown): valueArg is ILoqedStatusData => {
  return Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) && ('bolt_state' in valueArg || 'bridge_mac_wifi' in valueArg || 'battery_percentage' in valueArg));
};

const isLocked = (boltStateArg: string): boolean => {
  return ['night_lock_remote', 'night_lock', 'locked'].includes(boltStateArg);
};

const pushNumericEntity = (entitiesArg: ISimpleLocalEntitySnapshot[], idArg: string, nameArg: string, valueArg: unknown, unitArg?: string, deviceClassArg?: string): void => {
  const value = numberValue(valueArg);
  if (value === undefined) {
    return;
  }
  entitiesArg.push({
    id: idArg,
    name: nameArg,
    platform: 'sensor',
    state: value,
    available: true,
    writable: false,
    unit: unitArg,
    deviceClass: deviceClassArg,
    attributes: { stateClass: 'measurement' },
  });
};

const boolValue = (valueArg: unknown, fallbackArg: boolean): boolean => {
  if (typeof valueArg === 'boolean') {
    return valueArg;
  }
  if (typeof valueArg === 'number') {
    return valueArg !== 0;
  }
  return fallbackArg;
};

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 hostFromUrl = (urlArg: string | undefined): string | undefined => {
  if (!urlArg) {
    return undefined;
  }
  try {
    return new URL(urlArg).host;
  } catch {
    return undefined;
  }
};

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

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