import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IIntegrationEvent, IServiceCallRequest } from '../../core/types.js';
import type {
  IOnvifCameraSnapshot,
  IOnvifClientCommand,
  IOnvifConfig,
  IOnvifEvent,
  IOnvifProfile,
  IOnvifSnapshot,
  IOnvifStream,
  TOnvifPtzMoveMode,
  TOnvifStreamProtocol,
} from './onvif.types.js';
import { onvifDefaultPort } from './onvif.types.js';

const cameraServiceNames = new Set(['stream', 'stream_source', 'get_stream', 'stream_metadata']);
const snapshotServiceNames = new Set(['snapshot', 'camera_image', 'camera_snapshot', 'snapshot_metadata']);
const ptzMoveModes = new Set<TOnvifPtzMoveMode>(['ContinuousMove', 'RelativeMove', 'AbsoluteMove', 'GotoPreset', 'Stop']);

export class OnvifMapper {
  public static toSnapshot(configArg: IOnvifConfig, connectedArg?: boolean, eventsArg: IOnvifEvent[] = []): IOnvifSnapshot {
    const source = configArg.snapshot;
    const cameras = this.uniqueCameras([
      ...(source?.cameras || []),
      ...(configArg.cameras || []),
      ...this.camerasFromManualEntries(configArg.manualEntries || []),
      this.cameraFromTopLevel(configArg, source, eventsArg),
    ].filter((cameraArg): cameraArg is IOnvifCameraSnapshot => Boolean(cameraArg)));

    return {
      id: configArg.id || source?.id || configArg.discovery?.id,
      name: configArg.name || source?.name || configArg.discovery?.name,
      host: configArg.host || source?.host || configArg.discovery?.host,
      port: configArg.port || source?.port || configArg.discovery?.port || onvifDefaultPort,
      transport: configArg.transport || source?.transport || configArg.discovery?.transport || 'http',
      connected: connectedArg ?? source?.connected ?? cameras.some((cameraArg) => cameraArg.online === true),
      configured: Boolean(configArg.host || source?.configured || cameras.length),
      deviceInfo: configArg.deviceInfo || source?.deviceInfo,
      capabilities: configArg.capabilities || source?.capabilities,
      profiles: [...(source?.profiles || []), ...(configArg.profiles || [])],
      streams: [...(source?.streams || []), ...(configArg.streams || [])],
      events: [...(source?.events || []), ...(configArg.events || []), ...eventsArg],
      services: source?.services,
      cameras,
      discovery: configArg.discovery || source?.discovery,
      metadata: {
        ...source?.metadata,
        ...configArg.metadata,
        livePtzImplemented: false,
        liveEventsImplemented: false,
      },
    };
  }

  public static toDevices(snapshotArg: IOnvifSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = new Date().toISOString();
    return this.cameras(snapshotArg).map((cameraArg) => {
      const deviceId = this.cameraDeviceId(cameraArg, snapshotArg);
      const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
        { id: 'availability', capability: 'sensor', name: 'Availability', readable: true, writable: false },
      ];
      const state: plugins.shxInterfaces.data.IDeviceState[] = [
        { featureId: 'availability', value: cameraArg.online === false ? 'offline' : snapshotArg.connected || cameraArg.online ? 'online' : 'configured', updatedAt },
      ];

      for (const profile of cameraArg.profiles) {
        const stream = this.streamForProfile(cameraArg, profile);
        const streamFeatureId = `profile_${this.slug(profile.token)}_stream`;
        features.push({ id: streamFeatureId, capability: 'camera', name: `${this.profileName(profile)} Stream`, readable: true, writable: false });
        state.push({ featureId: streamFeatureId, value: this.streamState(profile, stream), updatedAt });

        if (cameraArg.capabilities?.snapshot || profile.snapshotUri) {
          const snapshotFeatureId = `profile_${this.slug(profile.token)}_snapshot`;
          features.push({ id: snapshotFeatureId, capability: 'camera', name: `${this.profileName(profile)} Snapshot`, readable: true, writable: false });
          state.push({ featureId: snapshotFeatureId, value: { uri: profile.snapshotUri || null, available: Boolean(profile.snapshotUri || cameraArg.capabilities?.snapshot) }, updatedAt });
        }

        if (cameraArg.capabilities?.ptz || profile.ptz) {
          const ptzFeatureId = `profile_${this.slug(profile.token)}_ptz`;
          features.push({ id: ptzFeatureId, capability: 'camera', name: `${this.profileName(profile)} PTZ`, readable: true, writable: true });
          state.push({ featureId: ptzFeatureId, value: { modes: this.ptzModes(profile), presets: profile.ptz?.presets || [] }, updatedAt });
        }
      }

      for (const event of cameraArg.events || []) {
        const featureId = `event_${this.slug(event.uid)}`;
        features.push({ id: featureId, capability: 'sensor', name: event.name, readable: true, writable: false, unit: event.unit });
        state.push({ featureId, value: this.deviceStateValue(event.value), updatedAt: new Date(event.timestamp || Date.now()).toISOString() });
      }

      return {
        id: deviceId,
        integrationDomain: 'onvif',
        name: this.cameraName(cameraArg, snapshotArg),
        protocol: 'http',
        manufacturer: cameraArg.deviceInfo?.manufacturer || snapshotArg.deviceInfo?.manufacturer || 'ONVIF',
        model: cameraArg.deviceInfo?.model || snapshotArg.deviceInfo?.model,
        online: cameraArg.online ?? snapshotArg.connected,
        features: this.uniqueFeatures(features),
        state,
        metadata: {
          host: cameraArg.host || snapshotArg.host,
          port: cameraArg.port || snapshotArg.port || onvifDefaultPort,
          transport: cameraArg.transport || snapshotArg.transport || 'http',
          macAddress: this.normalizeMac(cameraArg.deviceInfo?.macAddress || snapshotArg.deviceInfo?.macAddress),
          serialNumber: cameraArg.deviceInfo?.serialNumber || snapshotArg.deviceInfo?.serialNumber,
          firmwareVersion: cameraArg.deviceInfo?.firmwareVersion || snapshotArg.deviceInfo?.firmwareVersion,
          capabilities: cameraArg.capabilities || snapshotArg.capabilities,
          services: cameraArg.services || snapshotArg.services,
          cameraEntityPlatformConstraint: 'sensor',
          livePtzImplemented: false,
          liveEventsImplemented: false,
          ...cameraArg.metadata,
        },
      };
    });
  }

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

    for (const camera of this.cameras(snapshotArg)) {
      const deviceId = this.cameraDeviceId(camera, snapshotArg);
      const cameraName = this.cameraName(camera, snapshotArg);
      for (const profile of camera.profiles) {
        const stream = this.streamForProfile(camera, profile);
        const name = `${cameraName} ${this.profileName(profile)}`;
        entities.push(this.entity('sensor', `${name} Camera`, deviceId, `onvif_${this.slug(`${deviceId}_${profile.token}`)}`, stream?.uri || profile.streamUri ? 'available' : 'configured', usedIds, {
          capability: 'camera',
          platformConstraint: 'sensor',
          profileToken: profile.token,
          profileName: profile.name,
          streamUri: stream?.uri || profile.streamUri,
          streamProtocol: stream?.protocol || this.streamProtocol(stream?.uri || profile.streamUri),
          snapshotUri: profile.snapshotUri,
          encoding: profile.video?.encoding || stream?.encoding,
          resolution: profile.video?.resolution || stream?.resolution,
          ptz: profile.ptz,
          serviceMappings: {
            streamMetadata: 'camera.stream_metadata',
            snapshotMetadata: 'camera.snapshot_metadata',
            ptz: 'camera.ptz',
          },
          livePtzImplemented: false,
          liveEventsImplemented: false,
        }, camera.online !== false));
      }

      for (const event of camera.events || []) {
        entities.push(this.entity(event.platform, `${cameraName} ${event.name}`, deviceId, event.uid, this.eventState(event), usedIds, {
          deviceClass: event.deviceClass,
          unit: event.unit,
          entityCategory: event.entityCategory,
          topic: event.topic,
          metadata: event.metadata,
        }, camera.online !== false, undefined, event.entityEnabled !== false));
      }
    }

    return entities;
  }

  public static toIntegrationEvent(eventArg: IOnvifEvent): IIntegrationEvent {
    return {
      type: 'state_changed',
      integrationDomain: 'onvif',
      entityId: eventArg.uid,
      data: eventArg,
      timestamp: eventArg.timestamp || Date.now(),
    };
  }

  public static commandForService(snapshotArg: IOnvifSnapshot, requestArg: IServiceCallRequest): IOnvifClientCommand | undefined {
    if (requestArg.domain === 'onvif') {
      if (requestArg.service === 'snapshot' || requestArg.service === 'refresh') {
        return { type: requestArg.service, service: requestArg.service, target: requestArg.target, data: requestArg.data } as IOnvifClientCommand;
      }
      if (requestArg.service === 'subscribe_events') {
        return { type: 'subscribe_events', service: requestArg.service, target: requestArg.target, data: requestArg.data };
      }
    }

    const target = this.findTargetProfile(snapshotArg, requestArg);
    const profileToken = target?.profile.token || this.stringValue(requestArg.data?.profileToken || requestArg.data?.profile_token);

    if (requestArg.domain === 'camera' && cameraServiceNames.has(requestArg.service)) {
      return {
        type: 'stream_metadata',
        service: requestArg.service,
        entityId: requestArg.target.entityId,
        deviceId: requestArg.target.deviceId,
        profileToken,
        stream: { profileToken },
        target: requestArg.target,
        data: requestArg.data,
      };
    }

    if (requestArg.domain === 'camera' && snapshotServiceNames.has(requestArg.service)) {
      return {
        type: 'snapshot_metadata',
        service: requestArg.service,
        entityId: requestArg.target.entityId,
        deviceId: requestArg.target.deviceId,
        profileToken,
        snapshot: {
          profileToken,
          fetchImage: requestArg.service === 'camera_image' || requestArg.data?.fetchImage === true,
          width: this.numberValue(requestArg.data?.width),
          height: this.numberValue(requestArg.data?.height),
        },
        target: requestArg.target,
        data: requestArg.data,
      };
    }

    if (requestArg.domain === 'camera' && requestArg.service === 'ptz') {
      const moveMode = this.ptzMoveMode(requestArg.data?.move_mode || requestArg.data?.moveMode) || 'RelativeMove';
      return {
        type: 'ptz',
        service: requestArg.service,
        entityId: requestArg.target.entityId,
        deviceId: requestArg.target.deviceId,
        profileToken,
        ptz: {
          profileToken,
          moveMode,
          pan: this.stringValue(requestArg.data?.pan) as 'LEFT' | 'RIGHT' | undefined,
          tilt: this.stringValue(requestArg.data?.tilt) as 'UP' | 'DOWN' | undefined,
          zoom: this.stringValue(requestArg.data?.zoom) as 'ZOOM_IN' | 'ZOOM_OUT' | undefined,
          distance: this.numberValue(requestArg.data?.distance),
          speed: this.numberValue(requestArg.data?.speed),
          continuousDuration: this.numberValue(requestArg.data?.continuous_duration ?? requestArg.data?.continuousDuration),
          preset: this.stringValue(requestArg.data?.preset),
        },
        target: requestArg.target,
        data: requestArg.data,
      };
    }

    return undefined;
  }

  public static cameraDeviceId(cameraArg: IOnvifCameraSnapshot, snapshotArg?: IOnvifSnapshot): string {
    const info = cameraArg.deviceInfo || snapshotArg?.deviceInfo;
    const identifier = this.normalizeMac(info?.macAddress)
      || info?.serialNumber
      || cameraArg.id
      || snapshotArg?.id
      || cameraArg.host
      || snapshotArg?.host
      || 'configured';
    return `onvif.camera.${this.slug(identifier)}`;
  }

  public static normalizeMac(valueArg?: string): string | undefined {
    if (!valueArg) {
      return undefined;
    }
    const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
    if (compact.length !== 12) {
      return undefined;
    }
    return compact.match(/.{1,2}/g)?.join(':');
  }

  private static cameras(snapshotArg: IOnvifSnapshot): IOnvifCameraSnapshot[] {
    if (snapshotArg.cameras.length) {
      return snapshotArg.cameras;
    }
    return [{
      id: snapshotArg.id,
      name: snapshotArg.name,
      host: snapshotArg.host,
      port: snapshotArg.port,
      transport: snapshotArg.transport,
      online: snapshotArg.connected,
      deviceInfo: snapshotArg.deviceInfo,
      capabilities: snapshotArg.capabilities,
      profiles: snapshotArg.profiles || [],
      streams: snapshotArg.streams,
      events: snapshotArg.events,
      services: snapshotArg.services,
      metadata: snapshotArg.metadata,
    }];
  }

  private static cameraFromTopLevel(configArg: IOnvifConfig, sourceArg: IOnvifSnapshot | undefined, eventsArg: IOnvifEvent[]): IOnvifCameraSnapshot | undefined {
    const profiles = [...(sourceArg?.profiles || []), ...(configArg.profiles || [])];
    const streams = [...(sourceArg?.streams || []), ...(configArg.streams || [])];
    const events = [...(sourceArg?.events || []), ...(configArg.events || []), ...eventsArg];
    const hasTopLevel = Boolean(configArg.host || configArg.deviceInfo || configArg.capabilities || profiles.length || streams.length || events.length || sourceArg?.host || sourceArg?.deviceInfo);
    if (!hasTopLevel) {
      return undefined;
    }
    return {
      id: configArg.id || sourceArg?.id || configArg.discovery?.id,
      name: configArg.name || sourceArg?.name || configArg.discovery?.name,
      host: configArg.host || sourceArg?.host || configArg.discovery?.host,
      port: configArg.port || sourceArg?.port || configArg.discovery?.port || onvifDefaultPort,
      transport: configArg.transport || sourceArg?.transport || configArg.discovery?.transport || 'http',
      online: sourceArg?.connected,
      deviceInfo: configArg.deviceInfo || sourceArg?.deviceInfo,
      capabilities: configArg.capabilities || sourceArg?.capabilities,
      profiles,
      streams,
      events,
      services: sourceArg?.services,
      metadata: { ...sourceArg?.metadata, ...configArg.metadata },
    };
  }

  private static camerasFromManualEntries(entriesArg: NonNullable<IOnvifConfig['manualEntries']>): IOnvifCameraSnapshot[] {
    const cameras: IOnvifCameraSnapshot[] = [];
    for (const entry of entriesArg) {
      if (entry.snapshot) {
        cameras.push(...entry.snapshot.cameras);
        continue;
      }
      if (!entry.host && !entry.profiles?.length && !entry.deviceInfo) {
        continue;
      }
      cameras.push({
        id: entry.id,
        name: entry.name,
        host: entry.host,
        port: entry.port || onvifDefaultPort,
        transport: entry.transport || 'http',
        deviceInfo: entry.deviceInfo,
        capabilities: entry.capabilities,
        profiles: entry.profiles || [],
        streams: entry.streams,
        events: entry.events,
        metadata: entry.metadata,
      });
    }
    return cameras;
  }

  private static uniqueCameras(camerasArg: IOnvifCameraSnapshot[]): IOnvifCameraSnapshot[] {
    const seen = new Set<string>();
    const cameras: IOnvifCameraSnapshot[] = [];
    for (const camera of camerasArg) {
      const key = this.slug(this.normalizeMac(camera.deviceInfo?.macAddress) || camera.deviceInfo?.serialNumber || camera.id || camera.host || camera.name || String(cameras.length));
      if (seen.has(key)) {
        continue;
      }
      seen.add(key);
      cameras.push(camera);
    }
    return cameras;
  }

  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 cameraName(cameraArg: IOnvifCameraSnapshot, snapshotArg?: IOnvifSnapshot): string {
    return cameraArg.name || cameraArg.deviceInfo?.name || snapshotArg?.name || snapshotArg?.deviceInfo?.name || cameraArg.host || snapshotArg?.host || 'ONVIF Camera';
  }

  private static profileName(profileArg: IOnvifProfile): string {
    return profileArg.name || `Profile ${profileArg.index ?? profileArg.token}`;
  }

  private static streamForProfile(cameraArg: IOnvifCameraSnapshot, profileArg: IOnvifProfile): IOnvifStream | undefined {
    return cameraArg.streams?.find((streamArg) => streamArg.profileToken === profileArg.token)
      || (profileArg.streamUri ? {
        profileToken: profileArg.token,
        uri: profileArg.streamUri,
        protocol: this.streamProtocol(profileArg.streamUri),
        encoding: profileArg.video?.encoding,
        resolution: profileArg.video?.resolution,
      } : undefined);
  }

  private static streamState(profileArg: IOnvifProfile, streamArg?: IOnvifStream): Record<string, unknown> {
    return {
      profileToken: profileArg.token,
      uri: streamArg?.uri || profileArg.streamUri || null,
      protocol: streamArg?.protocol || this.streamProtocol(streamArg?.uri || profileArg.streamUri),
      encoding: streamArg?.encoding || profileArg.video?.encoding || null,
      resolution: streamArg?.resolution || profileArg.video?.resolution || null,
    };
  }

  private static streamProtocol(uriArg?: string): TOnvifStreamProtocol {
    if (!uriArg) {
      return 'unknown';
    }
    const protocol = uriArg.split(':', 1)[0]?.toLowerCase();
    return protocol === 'rtsp' || protocol === 'rtsps' || protocol === 'http' || protocol === 'https' ? protocol : 'unknown';
  }

  private static ptzModes(profileArg: IOnvifProfile): TOnvifPtzMoveMode[] {
    const modes: TOnvifPtzMoveMode[] = [];
    if (profileArg.ptz?.continuous) {
      modes.push('ContinuousMove');
    }
    if (profileArg.ptz?.relative) {
      modes.push('RelativeMove');
    }
    if (profileArg.ptz?.absolute) {
      modes.push('AbsoluteMove');
    }
    if (profileArg.ptz?.presets?.length) {
      modes.push('GotoPreset');
    }
    if (modes.length) {
      modes.push('Stop');
    }
    return modes;
  }

  private static findTargetProfile(snapshotArg: IOnvifSnapshot, requestArg: IServiceCallRequest): { camera: IOnvifCameraSnapshot; profile: IOnvifProfile } | undefined {
    const requestedToken = this.stringValue(requestArg.data?.profileToken || requestArg.data?.profile_token);
    for (const camera of this.cameras(snapshotArg)) {
      for (const profile of camera.profiles) {
        if (requestedToken && profile.token === requestedToken) {
          return { camera, profile };
        }
        if (requestArg.target.deviceId && this.cameraDeviceId(camera, snapshotArg) !== requestArg.target.deviceId) {
          continue;
        }
        if (requestArg.target.entityId) {
          const entityId = `sensor.${this.slug(`${this.cameraName(camera, snapshotArg)} ${this.profileName(profile)} Camera`)}`;
          if (requestArg.target.entityId !== entityId && requestArg.target.entityId !== `onvif_${this.slug(`${this.cameraDeviceId(camera, snapshotArg)}_${profile.token}`)}`) {
            continue;
          }
        }
        return { camera, profile };
      }
    }
    return undefined;
  }

  private static entity(platformArg: 'sensor' | 'binary_sensor', nameArg: string, deviceIdArg: string, uniqueIdArg: string, stateArg: unknown, usedIdsArg: Map<string, number>, attributesArg: Record<string, unknown>, availableArg: boolean, explicitIdArg?: string, enabledArg = true): IIntegrationEntity {
    const baseId = explicitIdArg || `${platformArg}.${this.slug(nameArg)}`;
    const id = this.uniqueEntityId(baseId, usedIdsArg);
    return {
      id,
      uniqueId: uniqueIdArg,
      integrationDomain: 'onvif',
      deviceId: deviceIdArg,
      platform: platformArg,
      name: nameArg,
      state: stateArg,
      attributes: {
        ...attributesArg,
        entityEnabled: enabledArg,
      },
      available: availableArg,
    };
  }

  private static uniqueEntityId(baseIdArg: string, usedIdsArg: Map<string, number>): string {
    const count = usedIdsArg.get(baseIdArg) || 0;
    usedIdsArg.set(baseIdArg, count + 1);
    return count ? `${baseIdArg}_${count + 1}` : baseIdArg;
  }

  private static eventState(eventArg: IOnvifEvent): unknown {
    if (eventArg.platform === 'binary_sensor') {
      return eventArg.value ? 'on' : 'off';
    }
    return eventArg.value ?? 'unknown';
  }

  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 (typeof valueArg === 'object') {
      return valueArg as Record<string, unknown>;
    }
    return String(valueArg);
  }

  private static ptzMoveMode(valueArg: unknown): TOnvifPtzMoveMode | undefined {
    const value = this.stringValue(valueArg);
    return value && ptzMoveModes.has(value as TOnvifPtzMoveMode) ? value as TOnvifPtzMoveMode : undefined;
  }

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

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

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