import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IIntegrationEvent, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type { IHikvisionBinarySensor, IHikvisionCameraChannel, IHikvisionClientCommand, IHikvisionEvent, IHikvisionSnapshot } from './hikvision.types.js';

const cameraStreamServices = new Set(['stream', 'stream_source', 'get_stream']);
const cameraSnapshotServices = new Set(['snapshot', 'camera_image', 'camera_snapshot']);
const eventMetadataServices = new Set(['event_triggers', 'event_triggers_metadata', 'binary_sensors']);
const unsupportedLiveEventServices = new Set(['subscribe_events', 'start_event_stream', 'enable_events']);

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

    for (const camera of snapshotArg.cameras) {
      const featureId = `camera_${this.slug(camera.id)}`;
      features.push({ id: featureId, capability: 'camera', name: camera.name || `Channel ${camera.channel ?? camera.id}`, readable: true, writable: false });
      state.push({
        featureId,
        value: {
          channel: camera.channel ?? null,
          streamingChannelId: camera.streamingChannelId ?? null,
          snapshotUrl: camera.snapshotUrl ?? null,
          rtspUrl: camera.rtspUrl ?? null,
          enabled: camera.enabled !== false,
        },
        updatedAt,
      });
    }

    for (const sensor of snapshotArg.binarySensors) {
      const featureId = `binary_${this.slug(`${sensor.key}_${sensor.channel}`)}`;
      features.push({ id: featureId, capability: 'sensor', name: this.binarySensorName(sensor), readable: true, writable: false });
      state.push({ featureId, value: sensor.isOn, updatedAt });
    }

    return [{
      id: this.deviceId(snapshotArg),
      integrationDomain: 'hikvision',
      name: this.deviceName(snapshotArg),
      protocol: 'http',
      manufacturer: snapshotArg.deviceInfo.manufacturer || 'Hikvision',
      model: snapshotArg.deviceInfo.model || snapshotArg.deviceInfo.deviceType,
      online: snapshotArg.connected,
      features: this.uniqueFeatures(features),
      state,
      metadata: {
        serialNumber: snapshotArg.deviceInfo.serialNumber,
        macAddress: snapshotArg.deviceInfo.macAddress,
        firmwareVersion: snapshotArg.deviceInfo.firmwareVersion,
        deviceType: snapshotArg.deviceInfo.deviceType,
        host: snapshotArg.deviceInfo.host,
        port: snapshotArg.deviceInfo.port,
        protocol: snapshotArg.deviceInfo.protocol,
        rtspPort: snapshotArg.deviceInfo.rtspPort,
        cameraStreams: snapshotArg.cameras.map((cameraArg) => ({
          id: cameraArg.id,
          channel: cameraArg.channel,
          streamingChannelId: cameraArg.streamingChannelId,
          snapshotUrl: cameraArg.snapshotUrl,
          rtspUrl: cameraArg.rtspUrl,
        })),
        eventTypes: snapshotArg.events.map((eventArg) => eventArg.sensorType || eventArg.rawType).filter(Boolean),
        ...snapshotArg.deviceInfo.attributes,
      },
    }];
  }

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

    for (const camera of snapshotArg.cameras) {
      entities.push(this.entity('camera' as TEntityPlatform, camera.name || `${this.deviceName(snapshotArg)} Camera ${camera.channel ?? camera.id}`, deviceId, `hikvision_${this.uniqueBase(snapshotArg)}_camera_${this.slug(camera.id)}`, snapshotArg.connected && camera.available !== false && camera.enabled !== false ? 'idle' : 'unavailable', usedIds, {
        cameraId: camera.id,
        channel: camera.channel,
        streamingChannelId: camera.streamingChannelId,
        snapshotUrl: camera.snapshotUrl,
        stillImageUrl: camera.snapshotUrl,
        rtspUrl: camera.rtspUrl,
        supportedFeatures: ['stream', 'snapshot'],
        serviceMappings: {
          snapshot: 'camera.snapshot',
          streamSource: 'camera.stream_source',
        },
        ...camera.attributes,
      }, snapshotArg.connected && camera.available !== false && camera.enabled !== false));
    }

    for (const sensor of snapshotArg.binarySensors) {
      entities.push(this.entity('binary_sensor', this.binarySensorName(sensor), deviceId, `hikvision_${this.uniqueBase(snapshotArg)}_${this.slug(sensor.key)}_${this.slug(String(sensor.channel))}`, sensor.isOn ? 'on' : 'off', usedIds, {
        key: sensor.key,
        sensorType: sensor.sensorType,
        channel: sensor.channel,
        deviceClass: sensor.deviceClass,
        entityCategory: sensor.entityCategory,
        lastTripTime: sensor.lastTripTime,
        eventId: sensor.eventId,
        triggerId: sensor.triggerId,
        ...sensor.attributes,
      }, sensor.available !== false));
    }

    for (const event of snapshotArg.events) {
      entities.push(this.entity('event' as TEntityPlatform, event.name || event.sensorType || event.id, deviceId, `hikvision_${this.uniqueBase(snapshotArg)}_event_${this.slug(event.id)}`, event.state || (event.isOn ? 'on' : 'off'), usedIds, {
        eventId: event.id,
        key: event.key,
        sensorType: event.sensorType,
        channel: event.channel,
        deviceClass: event.deviceClass,
        entityCategory: event.entityCategory,
        eventType: event.eventType,
        rawType: event.rawType,
        triggerId: event.triggerId,
        updatedAt: event.updatedAt,
        payload: event.payload,
      }, true));
    }

    return entities;
  }

  public static commandForService(snapshotArg: IHikvisionSnapshot, requestArg: IServiceCallRequest): IHikvisionClientCommand | undefined {
    if (requestArg.domain === 'hikvision') {
      if (requestArg.service === 'refresh') {
        return { type: 'refresh', service: requestArg.service, target: requestArg.target, data: requestArg.data };
      }
      if (cameraStreamServices.has(requestArg.service) || cameraSnapshotServices.has(requestArg.service)) {
        return this.commandForService(snapshotArg, { ...requestArg, domain: 'camera' });
      }
      if (eventMetadataServices.has(requestArg.service)) {
        return { type: 'event_triggers_metadata', service: requestArg.service, target: requestArg.target, data: requestArg.data };
      }
      if (unsupportedLiveEventServices.has(requestArg.service)) {
        return { type: 'subscribe_events', service: requestArg.service, target: requestArg.target, data: requestArg.data };
      }
    }

    if (requestArg.domain === 'camera' && cameraStreamServices.has(requestArg.service)) {
      const camera = this.findCamera(snapshotArg, requestArg);
      return {
        type: 'stream_source',
        service: requestArg.service,
        target: requestArg.target,
        data: requestArg.data,
        cameraId: camera?.id,
        channel: camera?.channel,
        streamingChannelId: camera?.streamingChannelId,
      };
    }

    if (requestArg.domain === 'camera' && cameraSnapshotServices.has(requestArg.service)) {
      const camera = this.findCamera(snapshotArg, requestArg);
      return {
        type: 'snapshot_image',
        service: requestArg.service,
        target: requestArg.target,
        data: requestArg.data,
        cameraId: camera?.id,
        channel: camera?.channel,
        streamingChannelId: camera?.streamingChannelId,
        filename: this.stringValue(requestArg.data?.filename),
        httpCommands: camera?.streamingChannelId ? [{ label: 'snapshot', method: 'GET', path: `/ISAPI/Streaming/channels/${camera.streamingChannelId}/picture`, expect: 'image' }] : undefined,
      };
    }

    return undefined;
  }

  public static toIntegrationEvent(eventArg: IHikvisionEvent): IIntegrationEvent {
    return {
      type: 'state_changed',
      integrationDomain: 'hikvision',
      entityId: eventArg.id,
      data: eventArg,
      timestamp: eventArg.updatedAt ? Date.parse(eventArg.updatedAt) : Date.now(),
    };
  }

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

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

  private static findCamera(snapshotArg: IHikvisionSnapshot, requestArg: IServiceCallRequest): IHikvisionCameraChannel | undefined {
    const target = requestArg.target.entityId
      || this.stringValue(requestArg.data?.cameraId ?? requestArg.data?.camera_id ?? requestArg.data?.camera ?? requestArg.data?.channel ?? requestArg.data?.streamingChannelId ?? requestArg.data?.streaming_channel_id);
    if (!target) {
      return snapshotArg.cameras[0];
    }
    const entities = this.toEntities(snapshotArg).filter((entityArg) => entityArg.platform === ('camera' as TEntityPlatform));
    const entity = entities.find((entityArg) => entityArg.id === target || entityArg.uniqueId === target || entityArg.attributes?.cameraId === target || entityArg.attributes?.channel === target || entityArg.attributes?.streamingChannelId === target);
    const cameraId = this.stringValue(entity?.attributes?.cameraId) || target;
    return snapshotArg.cameras.find((cameraArg) => cameraArg.id === cameraId || String(cameraArg.channel) === cameraId || cameraArg.streamingChannelId === cameraId) || snapshotArg.cameras[0];
  }

  private static binarySensorName(sensorArg: IHikvisionBinarySensor): string {
    const channel = String(sensorArg.channel);
    const base = sensorArg.name || sensorArg.sensorType || sensorArg.key;
    return channel ? `${base} channel ${channel}` : base;
  }

  private static uniqueFeatures(featuresArg: plugins.shxInterfaces.data.IDeviceFeature[]): plugins.shxInterfaces.data.IDeviceFeature[] {
    const seen = new Set<string>();
    return featuresArg.filter((featureArg) => {
      if (seen.has(featureArg.id)) {
        return false;
      }
      seen.add(featureArg.id);
      return true;
    });
  }

  private static deviceName(snapshotArg: IHikvisionSnapshot): string {
    return snapshotArg.deviceInfo.name || snapshotArg.deviceInfo.host || 'Hikvision device';
  }

  private static uniqueBase(snapshotArg: IHikvisionSnapshot): 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 stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : typeof valueArg === 'number' ? String(valueArg) : undefined;
  }

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