import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type {
  IAndroidIpWebcamClientCommand,
  IAndroidIpWebcamSensor,
  IAndroidIpWebcamSnapshot,
  IAndroidIpWebcamSwitch,
  TAndroidIpWebcamOrientation,
  TAndroidIpWebcamRtspAudioCodec,
  TAndroidIpWebcamRtspVideoCodec,
} from './android_ip_webcam.types.js';
import { androidIpWebcamSwitchDescriptions } from './android_ip_webcam.types.js';

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

export class AndroidIpWebcamMapper {
  public static toDevices(snapshotArg: IAndroidIpWebcamSnapshot): 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: false },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'connectivity', value: snapshotArg.connected ? 'online' : 'offline', updatedAt },
      { featureId: 'camera', value: { mjpegUrl: snapshotArg.camera.mjpegUrl || null, imageUrl: snapshotArg.camera.imageUrl || null, rtspUrl: snapshotArg.camera.rtspUrl || null }, updatedAt },
    ];

    for (const sensor of snapshotArg.sensors) {
      features.push({ id: `sensor_${this.slug(sensor.key)}`, capability: 'sensor', name: sensor.name, readable: true, writable: false, unit: sensor.unit });
      state.push({ featureId: `sensor_${this.slug(sensor.key)}`, value: this.deviceStateValue(sensor.value), updatedAt });
    }
    for (const sensor of snapshotArg.binarySensors) {
      features.push({ id: `binary_${this.slug(sensor.key)}`, capability: 'sensor', name: sensor.name, readable: true, writable: false });
      state.push({ featureId: `binary_${this.slug(sensor.key)}`, value: sensor.isOn, updatedAt });
    }
    for (const setting of snapshotArg.switches) {
      features.push({ id: `setting_${this.slug(setting.key)}`, capability: 'switch', name: setting.name, readable: true, writable: true });
      state.push({ featureId: `setting_${this.slug(setting.key)}`, value: setting.isOn, updatedAt });
    }

    return [{
      id: this.deviceId(snapshotArg),
      integrationDomain: 'android_ip_webcam',
      name: this.deviceName(snapshotArg),
      protocol: 'http',
      manufacturer: snapshotArg.deviceInfo.manufacturer || 'Android IP Webcam',
      model: snapshotArg.deviceInfo.model,
      online: snapshotArg.connected,
      features,
      state,
      metadata: {
        host: snapshotArg.deviceInfo.host,
        port: snapshotArg.deviceInfo.port,
        protocol: snapshotArg.deviceInfo.protocol,
        url: snapshotArg.deviceInfo.url,
        enabledSensors: snapshotArg.enabledSensors,
        enabledSettings: snapshotArg.enabledSettings,
        availableSettings: snapshotArg.availableSettings,
        camera: {
          mjpegUrl: snapshotArg.camera.mjpegUrl,
          imageUrl: snapshotArg.camera.imageUrl,
          rtspUrl: snapshotArg.camera.rtspUrl,
        },
      },
    }];
  }

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

    entities.push(this.entity('camera' as TEntityPlatform, snapshotArg.camera.name || `${baseName} Camera`, deviceId, `android_ip_webcam_${this.uniqueBase(snapshotArg)}_camera`, snapshotArg.connected ? 'idle' : 'unavailable', usedIds, {
      mjpegUrl: snapshotArg.camera.mjpegUrl,
      stillImageUrl: snapshotArg.camera.imageUrl,
      streamSource: snapshotArg.camera.rtspUrl,
      audioWavUrl: snapshotArg.camera.audioWavUrl,
      audioAacUrl: snapshotArg.camera.audioAacUrl,
      audioOpusUrl: snapshotArg.camera.audioOpusUrl,
      supportedFeatures: snapshotArg.camera.supportedFeatures || ['stream'],
      serviceMappings: {
        snapshot: 'camera.snapshot',
        streamSource: 'camera.stream_source',
      },
      ...snapshotArg.camera.attributes,
    }, snapshotArg.connected && snapshotArg.camera.available !== false));

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

    return entities;
  }

  public static commandForService(snapshotArg: IAndroidIpWebcamSnapshot, requestArg: IServiceCallRequest): IAndroidIpWebcamClientCommand | undefined {
    if (requestArg.domain === 'camera' && cameraStreamServices.has(requestArg.service)) {
      return {
        type: 'stream_source',
        service: requestArg.service,
        target: requestArg.target,
        data: requestArg.data,
        videoCodec: this.videoCodec(requestArg.data?.video_codec ?? requestArg.data?.videoCodec) || 'h264',
        audioCodec: this.audioCodec(requestArg.data?.audio_codec ?? requestArg.data?.audioCodec) || 'aac',
      };
    }
    if (requestArg.domain === 'camera' && cameraSnapshotServices.has(requestArg.service)) {
      return {
        type: 'snapshot_image',
        service: requestArg.service,
        target: requestArg.target,
        data: requestArg.data,
        filename: this.stringValue(requestArg.data?.filename),
      };
    }
    if (requestArg.domain === 'switch' && ['turn_on', 'turn_off', 'toggle'].includes(requestArg.service)) {
      const setting = this.findSwitch(snapshotArg, requestArg);
      if (!setting) {
        return undefined;
      }
      const activate = requestArg.service === 'turn_on' ? true : requestArg.service === 'turn_off' ? false : !setting.isOn;
      return this.commandForSwitch(setting, requestArg, activate);
    }
    if (requestArg.domain === 'android_ip_webcam') {
      return this.androidIpWebcamCommand(snapshotArg, requestArg);
    }
    return undefined;
  }

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

  private static sensorEntity(sensorArg: IAndroidIpWebcamSensor, deviceIdArg: string, snapshotArg: IAndroidIpWebcamSnapshot, usedIdsArg: Map<string, number>): IIntegrationEntity {
    return this.entity('sensor', sensorArg.name, deviceIdArg, `android_ip_webcam_${this.uniqueBase(snapshotArg)}_${this.slug(sensorArg.key)}`, sensorArg.value ?? 'unknown', usedIdsArg, {
      key: sensorArg.key,
      unit: sensorArg.unit,
      deviceClass: sensorArg.deviceClass,
      stateClass: sensorArg.stateClass,
      entityCategory: sensorArg.entityCategory,
      ...sensorArg.attributes,
    }, snapshotArg.connected && sensorArg.available !== false);
  }

  private static androidIpWebcamCommand(snapshotArg: IAndroidIpWebcamSnapshot, requestArg: IServiceCallRequest): IAndroidIpWebcamClientCommand | undefined {
    if (requestArg.service === 'refresh') {
      return { type: 'refresh', service: requestArg.service, target: requestArg.target, data: requestArg.data };
    }
    if (cameraStreamServices.has(requestArg.service)) {
      return { type: 'stream_source', service: requestArg.service, target: requestArg.target, data: requestArg.data, videoCodec: this.videoCodec(requestArg.data?.video_codec ?? requestArg.data?.videoCodec) || 'h264', audioCodec: this.audioCodec(requestArg.data?.audio_codec ?? requestArg.data?.audioCodec) || 'aac' };
    }
    if (cameraSnapshotServices.has(requestArg.service)) {
      return { type: 'snapshot_image', service: requestArg.service, target: requestArg.target, data: requestArg.data, filename: this.stringValue(requestArg.data?.filename) };
    }
    if (directSettingServices.has(requestArg.service)) {
      const key = this.stringValue(requestArg.data?.key ?? requestArg.data?.setting);
      if (!key || requestArg.data?.value === undefined) {
        return undefined;
      }
      return { type: 'setting', service: requestArg.service, target: requestArg.target, data: requestArg.data, key, value: requestArg.data.value };
    }
    if (requestArg.service === 'set_zoom') {
      const zoom = this.numberValue(requestArg.data?.zoom);
      return zoom === undefined ? undefined : { type: 'set_zoom', service: requestArg.service, target: requestArg.target, data: requestArg.data, zoom };
    }
    if (requestArg.service === 'set_quality') {
      const quality = this.numberValue(requestArg.data?.quality);
      return quality === undefined ? undefined : { type: 'set_quality', service: requestArg.service, target: requestArg.target, data: requestArg.data, quality };
    }
    if (requestArg.service === 'set_orientation') {
      const orientation = this.orientationValue(requestArg.data?.orientation);
      return orientation ? { type: 'set_orientation', service: requestArg.service, target: requestArg.target, data: requestArg.data, orientation } : undefined;
    }
    if (requestArg.service === 'set_scenemode') {
      const scenemode = this.stringValue(requestArg.data?.scenemode ?? requestArg.data?.scene_mode);
      return scenemode ? { type: 'set_scenemode', service: requestArg.service, target: requestArg.target, data: requestArg.data, scenemode } : undefined;
    }

    const settingKey = requestArg.service.startsWith('set_') ? requestArg.service.slice(4) : requestArg.service;
    const description = androidIpWebcamSwitchDescriptions.find((descriptionArg) => descriptionArg.key === settingKey);
    if (!description) {
      return undefined;
    }
    const snapshotSwitch = snapshotArg.switches.find((switchArg) => switchArg.key === description.key) || { ...description, isOn: false, available: true } as IAndroidIpWebcamSwitch;
    const activate = this.booleanFromData(requestArg.data) ?? true;
    return this.commandForSwitch(snapshotSwitch, requestArg, activate);
  }

  private static commandForSwitch(settingArg: IAndroidIpWebcamSwitch, requestArg: IServiceCallRequest, activateArg: boolean): IAndroidIpWebcamClientCommand {
    if (settingArg.command === 'torch') {
      return { type: 'torch', service: requestArg.service, target: requestArg.target, data: requestArg.data, key: settingArg.key, activate: activateArg };
    }
    if (settingArg.command === 'focus') {
      return { type: 'focus', service: requestArg.service, target: requestArg.target, data: requestArg.data, key: settingArg.key, activate: activateArg };
    }
    if (settingArg.command === 'record') {
      return { type: 'record', service: requestArg.service, target: requestArg.target, data: requestArg.data, key: settingArg.key, record: activateArg, tag: this.stringValue(requestArg.data?.tag) };
    }
    return { type: 'setting', service: requestArg.service, target: requestArg.target, data: requestArg.data, key: settingArg.key, value: activateArg };
  }

  private static findSwitch(snapshotArg: IAndroidIpWebcamSnapshot, requestArg: IServiceCallRequest): IAndroidIpWebcamSwitch | undefined {
    const target = requestArg.target.entityId || requestArg.target.deviceId || this.stringValue(requestArg.data?.entity_id ?? requestArg.data?.entityId ?? requestArg.data?.key ?? requestArg.data?.setting);
    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 || `android_ip_webcam.device.${this.uniqueBase(snapshotArg)}` === target);
  }

  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: 'android_ip_webcam',
      deviceId: deviceIdArg,
      platform: platformArg,
      name: nameArg,
      state: stateArg,
      attributes: this.cleanAttributes(attributesArg),
      available: availableArg,
    };
  }

  private static deviceName(snapshotArg: IAndroidIpWebcamSnapshot): string {
    return snapshotArg.deviceInfo.name || snapshotArg.deviceInfo.host || 'Android IP Webcam';
  }

  private static uniqueBase(snapshotArg: IAndroidIpWebcamSnapshot): string {
    return this.slug(snapshotArg.deviceInfo.id || snapshotArg.deviceInfo.host || snapshotArg.deviceInfo.url || 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 orientationValue(valueArg: unknown): TAndroidIpWebcamOrientation | undefined {
    const value = this.stringValue(valueArg);
    return value === 'landscape' || value === 'upsidedown' || value === 'portrait' || value === 'upsidedown_portrait' ? value : undefined;
  }

  private static videoCodec(valueArg: unknown): TAndroidIpWebcamRtspVideoCodec | undefined {
    const value = this.stringValue(valueArg);
    return value === 'jpeg' || value === 'h264' ? value : undefined;
  }

  private static audioCodec(valueArg: unknown): TAndroidIpWebcamRtspAudioCodec | undefined {
    const value = this.stringValue(valueArg);
    return value === 'ulaw' || value === 'alaw' || value === 'pcm' || value === 'opus' || value === 'aac' ? value : undefined;
  }

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