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 { IGenericConfig } from './generic.types.js';
import { genericDefaultName, genericProfile } from './generic.types.js';

export class GenericMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IGenericConfig>, 'profile'>): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({
      ...optionsArg,
      profile: genericProfile,
      rawData: this.normalizeRawData(optionsArg.config, optionsArg.rawData),
    });
  }

  public static toSnapshotFromRaw(configArg: IGenericConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
    return this.toSnapshot({ config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
  }

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

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

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

  private static normalizeRawData(configArg: IGenericConfig, rawDataArg: unknown): unknown {
    if (isSnapshotRecord(rawDataArg)) {
      return rawDataArg;
    }

    const rawObject = isRecord(rawDataArg) ? rawDataArg : undefined;
    const advanced = recordValue(rawObject?.advanced);
    const stillImageUrl = stringValue(configArg.stillImageUrl) || stringValue(configArg.still_image_url) || stringValue(rawObject?.stillImageUrl) || stringValue(rawObject?.still_image_url);
    const streamSource = stringValue(configArg.streamSource) || stringValue(configArg.stream_source) || stringValue(rawObject?.streamSource) || stringValue(rawObject?.stream_source);
    const contentType = stringValue(configArg.contentType) || stringValue(configArg.content_type) || stringValue(rawObject?.contentType) || stringValue(rawObject?.content_type) || (stillImageUrl ? 'image/jpeg' : undefined);
    const framerate = numberValue(configArg.framerate) ?? numberValue(rawObject?.framerate) ?? numberValue(advanced?.framerate);
    const verifySsl = booleanValue(configArg.verifySsl) ?? booleanValue(configArg.verify_ssl) ?? booleanValue(rawObject?.verifySsl) ?? booleanValue(rawObject?.verify_ssl) ?? booleanValue(advanced?.verify_ssl);
    const rtspTransport = stringValue(configArg.rtspTransport) || stringValue(configArg.rtsp_transport) || stringValue(rawObject?.rtspTransport) || stringValue(rawObject?.rtsp_transport) || stringValue(advanced?.rtsp_transport);

    if (!stillImageUrl && !streamSource && !contentType) {
      return rawDataArg;
    }

    const name = configArg.name || stringValue(rawObject?.name) || genericDefaultName;
    const endpoint = streamSource || stillImageUrl;
    const entities: ISimpleLocalEntitySnapshot[] = [{
      id: 'camera',
      name: 'Camera',
      platform: 'sensor',
      state: streamSource ? 'streaming' : 'still',
      available: true,
      writable: false,
      attributes: {
        homeAssistantPlatform: 'camera',
        stillImageUrl,
        streamSource,
        contentType,
        framerate,
        verifySsl,
        rtspTransport,
        authentication: stringValue(configArg.authentication) || stringValue(rawObject?.authentication) || stringValue(advanced?.authentication),
      },
    }];

    return {
      device: {
        id: configArg.uniqueId || endpoint || name,
        name,
        manufacturer: genericProfile.manufacturer,
        model: genericProfile.model,
        host: configArg.host || hostFromUrl(endpoint),
        port: configArg.port,
        protocol: protocolFromUrl(endpoint),
        configurationUrl: endpoint,
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const isSnapshotRecord = (valueArg: unknown): valueArg is ISimpleLocalSnapshot => Boolean(valueArg && typeof valueArg === 'object' && 'device' in valueArg && 'entities' in valueArg);

const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg));

const recordValue = (valueArg: unknown): Record<string, unknown> | undefined => isRecord(valueArg) ? valueArg : undefined;

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

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

const booleanValue = (valueArg: unknown): boolean | undefined => typeof valueArg === 'boolean' ? valueArg : undefined;

const hostFromUrl = (valueArg: string | undefined): string | undefined => {
  try {
    return valueArg ? new URL(valueArg).hostname : undefined;
  } catch {
    return undefined;
  }
};

const protocolFromUrl = (valueArg: string | undefined): 'http' | 'https' | 'tcp' | 'local' | 'unknown' => {
  if (!valueArg) {
    return 'http';
  }
  if (valueArg.startsWith('https://')) {
    return 'https';
  }
  if (valueArg.startsWith('rtsp://') || valueArg.startsWith('rtsps://')) {
    return 'tcp';
  }
  if (valueArg.startsWith('http://')) {
    return 'http';
  }
  return 'unknown';
};
