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 { IPhilipsJsConfig, IPhilipsJsRawApiData, IPhilipsJsSystemInfo, TPhilipsJsRawData } from './philips_js.types.js';
import { philipsJsDefaultApiVersion, philipsJsDefaultHttpPort, philipsJsDefaultHttpsPort, philipsJsDefaultName, philipsJsProfile } from './philips_js.types.js';

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

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

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

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

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

  public static normalizeRawData(rawDataArg: unknown, configArg: IPhilipsJsConfig): unknown {
    const parsedRawData = parseMaybeJson(rawDataArg);
    const rawObject = objectValue(parsedRawData);
    if (!rawObject || Array.isArray(rawObject.entities) || rawObject.device) {
      return parsedRawData;
    }

    const system = objectValue(rawObject.system) as IPhilipsJsSystemInfo | undefined;
    if (!system && !rawObject.powerstate && !rawObject.volume && !rawObject.ambilightMode) {
      return parsedRawData;
    }

    const endpoint = endpointFromConfig(configArg);
    const apiVersion = numberValue(system?.api_version?.Major) || numberValue(rawObject.apiVersion) || configArg.apiVersion || philipsJsDefaultApiVersion;
    const powerstate = stringValue(objectValue(rawObject.powerstate)?.powerstate);
    const name = configArg.name || stringValue(system?.name) || philipsJsDefaultName;
    const serialNumber = stringValue(system?.serialnumber) || stringValue(system?.deviceid);
    const entities = entitiesFromApi(rawObject as IPhilipsJsRawApiData, name, apiVersion);

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || serialNumber || (endpoint.host ? `${endpoint.host}:${endpoint.port}` : undefined) || philipsJsDefaultName,
        name,
        manufacturer: 'Philips',
        model: stringValue(system?.model) || 'Philips TV',
        serialNumber,
        host: endpoint.host,
        port: endpoint.port,
        protocol: endpoint.protocol,
        attributes: cleanAttributes({
          apiVersion,
          powerstate,
          softwareVersion: stringValue(system?.softwareversion),
          deviceId: stringValue(system?.deviceid),
        }),
      },
      entities,
      ...rawObject,
      system,
      fetchedAt: stringValue(rawObject.fetchedAt) || new Date().toISOString(),
    } satisfies IPhilipsJsRawApiData & { device: Record<string, unknown>; entities: ISimpleLocalEntitySnapshot[] };
  }
}

const entitiesFromApi = (rawArg: IPhilipsJsRawApiData, nameArg: string, apiVersionArg: number): ISimpleLocalEntitySnapshot[] => {
  const entities: ISimpleLocalEntitySnapshot[] = [];
  const powerstate = stringValue(rawArg.powerstate?.powerstate);
  const volume = rawArg.volume || {};
  const maxVolume = numberValue(volume.max);
  const currentVolume = numberValue(volume.current);
  const volumeLevel = currentVolume !== undefined && maxVolume ? currentVolume / maxVolume : undefined;
  const sourceId = sourceIdFromRaw(rawArg);
  const sourceName = sourceNameFromRaw(rawArg, sourceId);
  const app = currentApplication(rawArg);
  const appId = appIdFromApplication(app);
  const appName = appNameFromRaw(rawArg, appId);
  const channelId = channelIdFromRaw(rawArg);

  entities.push({
    id: 'media_player',
    name: nameArg,
    platform: 'media_player',
    state: mediaState(powerstate),
    writable: true,
    attributes: cleanAttributes({
      apiVersion: apiVersionArg,
      powerstate,
      volume: currentVolume,
      volumeMin: numberValue(volume.min),
      volumeMax: maxVolume,
      volumeLevel,
      isVolumeMuted: boolValue(volume.muted),
      source: sourceName || sourceId,
      sourceId,
      sourceList: sourceList(rawArg),
      appId,
      appName,
      channelId,
      channelListId: stringValue(objectValue(rawArg.channel?.channelList)?.id),
    }),
  });

  entities.push({
    id: 'remote',
    name: `${nameArg} Remote`,
    platform: 'button',
    state: remoteState(powerstate),
    writable: true,
    attributes: cleanAttributes({ powerstate }),
  });

  const ambilightState = ambilightPower(rawArg);
  if (ambilightState !== undefined || rawArg.ambilightMode || rawArg.ambilightCurrentConfiguration) {
    entities.push({
      id: 'ambilight',
      name: 'Ambilight',
      platform: 'light',
      state: ambilightState ?? true,
      writable: true,
      attributes: cleanAttributes({
        mode: stringValue(rawArg.ambilightMode?.current),
        power: stringValue(rawArg.ambilightPower?.power),
        styleName: stringValue(rawArg.ambilightCurrentConfiguration?.styleName),
        isExpert: boolValue(rawArg.ambilightCurrentConfiguration?.isExpert),
      }),
    });
  }

  const screenstate = stringValue(rawArg.screenstate?.screenstate);
  if (screenstate) {
    entities.push({
      id: 'screen_state',
      name: 'Screen State',
      platform: 'switch',
      state: screenstate === 'On',
      writable: true,
      attributes: { screenstate },
    });
  }

  const huePower = stringValue(rawArg.hueLampPower?.power);
  if (huePower) {
    entities.push({
      id: 'ambilight_hue',
      name: 'Ambilight Hue',
      platform: 'switch',
      state: huePower === 'On',
      writable: true,
      attributes: { power: huePower },
    });
  }

  const recordings = Array.isArray(rawArg.recordings?.recordings) ? rawArg.recordings.recordings : [];
  if (recordings.length) {
    entities.push({ id: 'recording_ongoing', name: 'Recording Ongoing', platform: 'binary_sensor', state: recordings.some((recordingArg) => objectValue(recordingArg)?.RecordingType === 'RECORDING_ONGOING'), attributes: { deviceClass: 'running' } });
    entities.push({ id: 'recording_new', name: 'Recording New', platform: 'binary_sensor', state: recordings.some((recordingArg) => objectValue(recordingArg)?.RecordingType === 'RECORDING_NEW') });
  }

  return entities;
};

const endpointFromConfig = (configArg: IPhilipsJsConfig): { host?: string; port?: number; protocol: 'http' | 'https' } => {
  const rawUrl = configArg.url || (typeof configArg.host === 'string' && configArg.host.includes('://') ? configArg.host : undefined);
  if (rawUrl) {
    try {
      const parsed = new URL(rawUrl.includes('://') ? rawUrl : `http://${rawUrl}`);
      const protocol = parsed.protocol === 'https:' ? 'https' : 'http';
      return { host: parsed.hostname, port: configArg.port || numberValue(parsed.port) || (protocol === 'https' ? philipsJsDefaultHttpsPort : philipsJsDefaultHttpPort), protocol };
    } catch {}
  }
  const protocol = configArg.useTls ? 'https' : 'http';
  return { host: configArg.host, port: configArg.port || (configArg.host ? (protocol === 'https' ? philipsJsDefaultHttpsPort : philipsJsDefaultHttpPort) : undefined), protocol };
};

const mediaState = (powerstateArg?: string): string => {
  if (!powerstateArg) {
    return 'on';
  }
  return powerstateArg === 'On' ? 'on' : 'off';
};

const remoteState = (powerstateArg?: string): boolean => !powerstateArg || powerstateArg === 'On';

const ambilightPower = (rawArg: IPhilipsJsRawApiData): boolean | undefined => {
  const power = stringValue(rawArg.ambilightPower?.power);
  if (power) {
    return power === 'On';
  }
  const mode = stringValue(rawArg.ambilightMode?.current);
  if (mode) {
    return mode !== 'OFF';
  }
  return undefined;
};

const sourceIdFromRaw = (rawArg: IPhilipsJsRawApiData): string | undefined => {
  return stringValue(rawArg.sourceCurrent?.id) || stringValue(objectValue(rawArg.channel?.channel)?.ccid) || stringValue(rawArg.channel?.id);
};

const sourceNameFromRaw = (rawArg: IPhilipsJsRawApiData, sourceIdArg?: string): string | undefined => {
  if (!sourceIdArg || !rawArg.sources) {
    return undefined;
  }
  return stringValue(objectValue(rawArg.sources[sourceIdArg])?.name);
};

const sourceList = (rawArg: IPhilipsJsRawApiData): string[] | undefined => {
  if (!rawArg.sources || typeof rawArg.sources !== 'object' || Array.isArray(rawArg.sources)) {
    return undefined;
  }
  const list = Object.entries(rawArg.sources).map(([keyArg, valueArg]) => stringValue(objectValue(valueArg)?.name) || keyArg);
  return list.length ? list : undefined;
};

const currentApplication = (rawArg: IPhilipsJsRawApiData): Record<string, unknown> | undefined => objectValue(rawArg.application);

const appIdFromApplication = (appArg: Record<string, unknown> | undefined): string | undefined => {
  const component = objectValue(appArg?.component);
  const className = stringValue(component?.className);
  const packageName = stringValue(component?.packageName);
  return className && packageName ? `${className}-${packageName}` : undefined;
};

const appNameFromRaw = (rawArg: IPhilipsJsRawApiData, appIdArg?: string): string | undefined => {
  const applications = Array.isArray(rawArg.applications?.applications) ? rawArg.applications.applications : [];
  const match = applications.map((itemArg) => objectValue(itemArg)).find((itemArg) => itemArg && stringValue(itemArg.id) === appIdArg);
  return stringValue(match?.label);
};

const channelIdFromRaw = (rawArg: IPhilipsJsRawApiData): string | undefined => {
  const activityChannel = objectValue(rawArg.channel?.channel);
  return stringValue(activityChannel?.ccid) || stringValue(rawArg.channel?.id);
};

const objectValue = (valueArg: unknown): Record<string, unknown> | undefined => {
  return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : undefined;
};

const parseMaybeJson = (valueArg: unknown): unknown => {
  if (typeof valueArg !== 'string') {
    return valueArg;
  }
  try {
    return JSON.parse(valueArg) as IPhilipsJsRawApiData;
  } catch {
    return valueArg;
  }
};

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

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

const boolValue = (valueArg: unknown): boolean | undefined => {
  if (typeof valueArg === 'boolean') {
    return valueArg;
  }
  if (typeof valueArg === 'string') {
    if (valueArg.toLowerCase() === 'true') {
      return true;
    }
    if (valueArg.toLowerCase() === 'false') {
      return false;
    }
  }
  return undefined;
};

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