import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type {
  IFoscamCommandRequest,
  IFoscamNumber,
  IFoscamSnapshot,
  IFoscamSwitch,
  TFoscamEntityKey,
  TFoscamPtzMovement,
} from './foscam.types.js';
import { foscamDomain, foscamPtzMovements } from './foscam.types.js';

const cameraStreamServices = new Set(['stream', 'stream_source', 'get_stream']);
const cameraSnapshotServices = new Set(['snapshot', 'camera_image', 'camera_snapshot']);
const serviceBooleanKeys = ['enabled', 'enable', 'on', 'state', 'value'];

export class FoscamMapper {
  public static toDevices(snapshotArg: IFoscamSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
      { id: 'camera', capability: 'camera', name: snapshotArg.camera.name || 'Camera', readable: true, writable: Boolean(snapshotArg.camera.supportsPtz) },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'connectivity', value: snapshotArg.connected ? 'online' : 'offline', updatedAt },
      { featureId: 'camera', value: this.deviceStateValue({ snapshotUrl: snapshotArg.camera.snapshotUrl || null, rtspUrl: snapshotArg.camera.rtspUrl || null, stream: snapshotArg.camera.stream, motionDetectionEnabled: snapshotArg.camera.motionDetectionEnabled ?? null }), updatedAt },
    ];

    for (const switchArg of snapshotArg.switches) {
      features.push({ id: `switch_${this.slug(switchArg.key)}`, capability: 'switch', name: switchArg.name, readable: true, writable: true });
      state.push({ featureId: `switch_${this.slug(switchArg.key)}`, value: switchArg.isOn, updatedAt });
    }
    for (const numberArg of snapshotArg.numbers) {
      features.push({ id: `number_${this.slug(numberArg.key)}`, capability: 'sensor', name: numberArg.name, readable: true, writable: true });
      state.push({ featureId: `number_${this.slug(numberArg.key)}`, value: numberArg.value, updatedAt });
    }

    return [{
      id: this.deviceId(snapshotArg),
      integrationDomain: foscamDomain,
      name: this.deviceName(snapshotArg),
      protocol: 'http',
      manufacturer: snapshotArg.deviceInfo.manufacturer || 'Foscam',
      model: snapshotArg.deviceInfo.model || snapshotArg.deviceInfo.productName,
      online: snapshotArg.connected,
      features,
      state,
      metadata: {
        serialNumber: snapshotArg.deviceInfo.serialNumber,
        macAddress: snapshotArg.deviceInfo.macAddress,
        firmwareVersion: snapshotArg.deviceInfo.firmwareVersion,
        hardwareVersion: snapshotArg.deviceInfo.hardwareVersion,
        host: snapshotArg.deviceInfo.host,
        port: snapshotArg.deviceInfo.port,
        protocol: snapshotArg.deviceInfo.protocol,
        rtspPort: snapshotArg.deviceInfo.rtspPort,
        stream: snapshotArg.camera.stream,
        source: snapshotArg.source,
        error: snapshotArg.error,
      },
    }];
  }

  public static toEntities(snapshotArg: IFoscamSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    const usedIds = new Map<string, number>();
    const deviceId = this.deviceId(snapshotArg);

    entities.push(this.entity('camera' as TEntityPlatform, snapshotArg.camera.name || `${this.deviceName(snapshotArg)} Camera`, deviceId, `${foscamDomain}_${this.uniqueBase(snapshotArg)}_camera`, snapshotArg.connected && snapshotArg.camera.available !== false ? 'idle' : 'unavailable', usedIds, {
      cameraId: snapshotArg.camera.id,
      stream: snapshotArg.camera.stream,
      streamSourceUrl: snapshotArg.camera.streamSourceUrl,
      snapshotUrl: snapshotArg.camera.snapshotUrl,
      stillImageUrl: snapshotArg.camera.snapshotUrl,
      rtspUrl: snapshotArg.camera.rtspUrl,
      supportedFeatures: snapshotArg.camera.supportsPtz ? ['stream', 'snapshot', 'ptz', 'motion_detection'] : ['stream', 'snapshot', 'motion_detection'],
      motionDetectionEnabled: snapshotArg.camera.motionDetectionEnabled,
      serviceMappings: {
        snapshot: 'camera.snapshot',
        streamSource: 'camera.stream_source',
        ptz: 'foscam.ptz',
        ptzPreset: 'foscam.ptz_preset',
      },
      ...snapshotArg.camera.attributes,
    }, snapshotArg.connected && snapshotArg.camera.available !== false));

    for (const switchArg of snapshotArg.switches) {
      entities.push(this.entity('switch', switchArg.name, deviceId, `${foscamDomain}_${this.uniqueBase(snapshotArg)}_${this.slug(switchArg.key)}`, switchArg.isOn ? 'on' : 'off', usedIds, {
        key: switchArg.key,
        entityCategory: switchArg.entityCategory,
        deviceClass: switchArg.deviceClass,
        ...switchArg.attributes,
      }, snapshotArg.connected && switchArg.available !== false));
    }

    for (const numberArg of snapshotArg.numbers) {
      entities.push(this.entity('number', numberArg.name, deviceId, `${foscamDomain}_${this.uniqueBase(snapshotArg)}_${this.slug(numberArg.key)}`, numberArg.value, usedIds, {
        key: numberArg.key,
        nativeMinValue: numberArg.min,
        nativeMaxValue: numberArg.max,
        nativeStep: numberArg.step,
        unit: numberArg.unit,
        entityCategory: numberArg.entityCategory,
        ...numberArg.attributes,
      }, snapshotArg.connected && numberArg.available !== false));
    }

    return entities;
  }

  public static commandForService(snapshotArg: IFoscamSnapshot, requestArg: IServiceCallRequest): IFoscamCommandRequest | undefined {
    if (requestArg.domain === 'camera' && cameraStreamServices.has(requestArg.service)) {
      return { type: 'stream_source', service: requestArg.service, target: requestArg.target, data: requestArg.data, cameraId: snapshotArg.camera.id };
    }
    if (requestArg.domain === 'camera' && cameraSnapshotServices.has(requestArg.service)) {
      return { type: 'snapshot_image', service: requestArg.service, target: requestArg.target, data: requestArg.data, cameraId: snapshotArg.camera.id, filename: this.stringValue(requestArg.data?.filename), httpCommands: [{ label: 'snapshot', method: 'GET', command: 'snapPicture2', expect: 'image' }] };
    }
    if (requestArg.domain === 'camera' && (requestArg.service === 'enable_motion_detection' || requestArg.service === 'disable_motion_detection')) {
      return { type: requestArg.service === 'enable_motion_detection' ? 'enable_motion_detection' : 'disable_motion_detection', service: requestArg.service, target: requestArg.target, data: requestArg.data, key: 'motion_detection' };
    }
    if (requestArg.domain === 'switch' && ['turn_on', 'turn_off', 'toggle'].includes(requestArg.service)) {
      const switchEntity = this.findSwitch(snapshotArg, requestArg);
      if (!switchEntity) {
        return undefined;
      }
      const enabled = requestArg.service === 'turn_on' ? true : requestArg.service === 'turn_off' ? false : !switchEntity.isOn;
      return { type: 'set_switch', service: requestArg.service, target: requestArg.target, data: requestArg.data, key: switchEntity.key, enabled, httpCommands: this.switchHttpCommands(switchEntity.key, enabled) };
    }
    if (requestArg.domain === 'number' && ['set_value', 'set_native_value'].includes(requestArg.service)) {
      const numberEntity = this.findNumber(snapshotArg, requestArg);
      const value = this.numberValue(requestArg.data?.value ?? requestArg.data?.native_value ?? requestArg.data?.nativeValue);
      if (!numberEntity || value === undefined) {
        return undefined;
      }
      return { type: 'set_number', service: requestArg.service, target: requestArg.target, data: requestArg.data, key: numberEntity.key, value, httpCommands: this.numberHttpCommands(numberEntity.key, value) };
    }
    if (requestArg.domain === foscamDomain) {
      return this.foscamCommand(snapshotArg, requestArg);
    }
    return undefined;
  }

  public static deviceId(snapshotArg: IFoscamSnapshot): string {
    return `${foscamDomain}.device.${this.uniqueBase(snapshotArg)}`;
  }

  private static foscamCommand(snapshotArg: IFoscamSnapshot, requestArg: IServiceCallRequest): IFoscamCommandRequest | undefined {
    if (requestArg.service === 'refresh') {
      return { type: 'refresh', service: requestArg.service, target: requestArg.target, data: requestArg.data };
    }
    if (requestArg.service === 'snapshot' || cameraSnapshotServices.has(requestArg.service)) {
      return this.commandForService(snapshotArg, { ...requestArg, domain: 'camera' });
    }
    if (requestArg.service === 'stream_source' || cameraStreamServices.has(requestArg.service)) {
      return this.commandForService(snapshotArg, { ...requestArg, domain: 'camera' });
    }
    if (requestArg.service === 'ptz') {
      const movement = this.ptzMovement(requestArg.data?.movement);
      if (!movement) {
        return undefined;
      }
      return { type: 'ptz', service: requestArg.service, target: requestArg.target, data: requestArg.data, cameraId: snapshotArg.camera.id, movement, travelTime: this.numberValue(requestArg.data?.travel_time ?? requestArg.data?.travelTime) ?? 0.125 };
    }
    if (requestArg.service === 'ptz_preset') {
      const presetName = this.stringValue(requestArg.data?.preset_name ?? requestArg.data?.presetName);
      return presetName ? { type: 'ptz_preset', service: requestArg.service, target: requestArg.target, data: requestArg.data, cameraId: snapshotArg.camera.id, presetName, httpCommands: [{ label: 'ptz_preset', method: 'GET', command: 'ptzGotoPresetPoint', params: { name: presetName }, expect: 'ok' }] } : undefined;
    }
    if (requestArg.service === 'set_switch') {
      const key = this.entityKey(requestArg.data?.key);
      const enabled = this.booleanFromData(requestArg.data);
      return key && enabled !== undefined ? { type: 'set_switch', service: requestArg.service, target: requestArg.target, data: requestArg.data, key, enabled, httpCommands: this.switchHttpCommands(key, enabled) } : undefined;
    }
    if (requestArg.service === 'set_number') {
      const key = this.entityKey(requestArg.data?.key);
      const value = this.numberValue(requestArg.data?.value);
      return key && value !== undefined ? { type: 'set_number', service: requestArg.service, target: requestArg.target, data: requestArg.data, key, value, httpCommands: this.numberHttpCommands(key, value) } : undefined;
    }
    return undefined;
  }

  private static switchHttpCommands(keyArg: TFoscamEntityKey, enabledArg: boolean) {
    if (keyArg === 'motion_detection') {
      return [{ label: 'motion_detection', method: 'GET' as const, command: 'setMotionDetectConfig', params: { isEnable: enabledArg ? 1 : 0 }, expect: 'ok' as const }];
    }
    if (keyArg === 'is_flip') {
      return [{ label: 'flip', method: 'GET' as const, command: 'flipVideo', params: { isFlip: enabledArg ? 1 : 0 }, expect: 'ok' as const }];
    }
    if (keyArg === 'is_mirror') {
      return [{ label: 'mirror', method: 'GET' as const, command: 'mirrorVideo', params: { isMirror: enabledArg ? 1 : 0 }, expect: 'ok' as const }];
    }
    if (keyArg === 'is_open_ir') {
      return [
        { label: 'infrared_mode', method: 'GET' as const, command: 'setInfraLedConfig', params: { mode: enabledArg ? 1 : 0 }, expect: 'ok' as const },
        { label: 'infrared_led', method: 'GET' as const, command: enabledArg ? 'openInfraLed' : 'closeInfraLed', expect: 'ok' as const },
      ];
    }
    if (keyArg === 'sleep_switch') {
      return [{ label: 'sleep', method: 'GET' as const, command: enabledArg ? 'alexaSleep' : 'alexaWakeUp', expect: 'ok' as const }];
    }
    if (keyArg === 'is_open_white_light') {
      return [{ label: 'white_light', method: 'GET' as const, command: enabledArg ? 'openWhiteLight' : 'closeWhiteLight', expect: 'ok' as const }];
    }
    if (keyArg === 'is_siren_alarm') {
      return [{ label: 'siren_alarm', method: 'GET' as const, command: 'setSirenConfig', params: { sirenEnable: enabledArg ? 1 : 0, sirenvolume: 100, reserved: 0 }, expect: 'ok' as const }];
    }
    if (keyArg === 'is_turn_off_volume') {
      return [{ label: 'volume_muted', method: 'GET' as const, command: 'setVoiceEnableState', params: { isEnable: enabledArg ? 0 : 1 }, expect: 'ok' as const }];
    }
    if (keyArg === 'is_turn_off_light') {
      return [{ label: 'light', method: 'GET' as const, command: 'setLedEnableState', params: { isEnable: enabledArg ? 1 : 0 }, expect: 'ok' as const }];
    }
    if (keyArg === 'is_open_hdr') {
      return [{ label: 'hdr', method: 'GET' as const, command: 'setHdrMode', params: { mode: enabledArg ? 1 : 0 }, expect: 'ok' as const }];
    }
    if (keyArg === 'is_open_wdr') {
      return [{ label: 'wdr', method: 'GET' as const, command: 'setWdrMode', params: { mode: enabledArg ? 1 : 0 }, expect: 'ok' as const }];
    }
    if (keyArg === 'pet_detection' || keyArg === 'car_detection' || keyArg === 'human_detection') {
      const field = keyArg === 'pet_detection' ? 'petEnable' : keyArg === 'car_detection' ? 'carEnable' : 'humanEnable';
      return [{ label: keyArg, method: 'GET' as const, command: 'setMotionDetectConfig', params: { [field]: enabledArg ? 1 : 0 }, expect: 'ok' as const }];
    }
    return [];
  }

  private static numberHttpCommands(keyArg: TFoscamEntityKey, valueArg: number) {
    const value = Math.max(0, Math.min(100, Math.round(valueArg)));
    if (keyArg === 'device_volume') {
      return [{ label: 'device_volume', method: 'GET' as const, command: 'setAudioVolume', params: { volume: value }, expect: 'ok' as const }];
    }
    if (keyArg === 'speak_volume') {
      return [{ label: 'speak_volume', method: 'GET' as const, command: 'setSpeakVolume', params: { SpeakVolume: value }, expect: 'ok' as const }];
    }
    return [];
  }

  private static entity(platformArg: TEntityPlatform, nameArg: string, deviceIdArg: string, uniqueIdArg: string, stateArg: unknown, usedIdsArg: Map<string, number>, attributesArg: Record<string, unknown>, availableArg: boolean): IIntegrationEntity {
    const baseId = `${String(platformArg)}.${this.slug(nameArg) || this.slug(uniqueIdArg)}`;
    const seen = usedIdsArg.get(baseId) || 0;
    usedIdsArg.set(baseId, seen + 1);
    return {
      id: seen ? `${baseId}_${seen + 1}` : baseId,
      uniqueId: uniqueIdArg,
      integrationDomain: foscamDomain,
      deviceId: deviceIdArg,
      platform: platformArg,
      name: nameArg,
      state: stateArg,
      attributes: this.cleanAttributes(attributesArg),
      available: availableArg,
    };
  }

  private static findSwitch(snapshotArg: IFoscamSnapshot, requestArg: IServiceCallRequest): IFoscamSwitch | undefined {
    const target = requestArg.target.entityId || requestArg.target.deviceId || this.stringValue(requestArg.data?.entity_id ?? requestArg.data?.entityId ?? requestArg.data?.key);
    if (!target) {
      return snapshotArg.switches[0];
    }
    const entities = this.toEntities(snapshotArg).filter((entityArg) => entityArg.platform === 'switch');
    const entity = entities.find((entityArg) => entityArg.id === target || entityArg.uniqueId === target || entityArg.attributes?.key === target);
    const key = this.stringValue(entity?.attributes?.key) || target;
    return snapshotArg.switches.find((switchArg) => switchArg.key === key || switchArg.name === target || this.deviceId(snapshotArg) === target);
  }

  private static findNumber(snapshotArg: IFoscamSnapshot, requestArg: IServiceCallRequest): IFoscamNumber | undefined {
    const target = requestArg.target.entityId || requestArg.target.deviceId || this.stringValue(requestArg.data?.entity_id ?? requestArg.data?.entityId ?? requestArg.data?.key);
    if (!target) {
      return snapshotArg.numbers[0];
    }
    const entities = this.toEntities(snapshotArg).filter((entityArg) => entityArg.platform === 'number');
    const entity = entities.find((entityArg) => entityArg.id === target || entityArg.uniqueId === target || entityArg.attributes?.key === target);
    const key = this.stringValue(entity?.attributes?.key) || target;
    return snapshotArg.numbers.find((numberArg) => numberArg.key === key || numberArg.name === target || this.deviceId(snapshotArg) === target);
  }

  private static deviceName(snapshotArg: IFoscamSnapshot): string {
    return snapshotArg.deviceInfo.name || snapshotArg.deviceInfo.host || 'Foscam Camera';
  }

  private static uniqueBase(snapshotArg: IFoscamSnapshot): string {
    return this.slug(snapshotArg.deviceInfo.macAddress || snapshotArg.deviceInfo.serialNumber || snapshotArg.deviceInfo.id || snapshotArg.deviceInfo.host || this.deviceName(snapshotArg));
  }

  private static cleanAttributes(attributesArg: Record<string, unknown>): Record<string, unknown> {
    return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  }

  private static deviceStateValue(valueArg: unknown): plugins.shxInterfaces.data.TDeviceStateValue {
    if (valueArg === undefined) {
      return null;
    }
    if (valueArg === null || typeof valueArg === 'string' || typeof valueArg === 'number' || typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg)) {
      return valueArg as Record<string, unknown>;
    }
    return String(valueArg);
  }

  private static booleanFromData(dataArg: Record<string, unknown> | undefined): boolean | undefined {
    for (const key of serviceBooleanKeys) {
      const value = this.booleanValue(dataArg?.[key]);
      if (value !== undefined) {
        return value;
      }
    }
    return undefined;
  }

  private static booleanValue(valueArg: unknown): boolean | undefined {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'string') {
      if (['on', 'true', 'yes', '1'].includes(valueArg.toLowerCase())) {
        return true;
      }
      if (['off', 'false', 'no', '0'].includes(valueArg.toLowerCase())) {
        return false;
      }
    }
    return undefined;
  }

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

  private static stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : typeof valueArg === 'number' ? String(valueArg) : undefined;
  }

  private static ptzMovement(valueArg: unknown): TFoscamPtzMovement | undefined {
    const value = this.stringValue(valueArg)?.toLowerCase();
    return value && foscamPtzMovements.includes(value as TFoscamPtzMovement) ? value as TFoscamPtzMovement : undefined;
  }

  private static entityKey(valueArg: unknown): TFoscamEntityKey | undefined {
    const value = this.stringValue(valueArg) as TFoscamEntityKey | undefined;
    return value ? value : undefined;
  }

  private static slug(valueArg: string): string {
    return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || foscamDomain;
  }
}
