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 { IKioskerApiData, IKioskerApiStatus, IKioskerBlackoutState, IKioskerConfig, IKioskerScreensaverState } from './kiosker.types.js';
import { kioskerDefaultPort, kioskerProfile } from './kiosker.types.js';

export class KioskerMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IKioskerConfig>, 'profile'>): ISimpleLocalSnapshot {
    const apiData = kioskerApiData(optionsArg.rawData);
    if (apiData) {
      return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: kioskerProfile, rawData: this.apiDataToSnapshot(optionsArg.config, apiData, optionsArg.online ?? true, optionsArg.source || 'http') });
    }

    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: kioskerProfile });
  }

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

  public static toSnapshotFromApiData(configArg: IKioskerConfig, apiDataArg: IKioskerApiData): ISimpleLocalSnapshot {
    return this.toSnapshot({ config: configArg, rawData: apiDataArg as unknown as TSimpleLocalRawData, online: true, source: 'http' });
  }

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

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

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

  private static apiDataToSnapshot(configArg: IKioskerConfig, apiDataArg: IKioskerApiData, onlineArg: boolean, sourceArg: ISimpleLocalSnapshot['source']): ISimpleLocalSnapshot {
    const status = apiDataArg.status;
    const blackout = apiDataArg.blackout ?? null;
    const screensaver = apiDataArg.screensaver ?? null;
    const deviceId = stringValue(status.deviceId) || stringValue(status.device_id) || configArg.uniqueId || configArg.deviceId || hostId(configArg) || 'kiosker';
    const shortId = deviceId.slice(0, 8).toUpperCase();
    const protocol = protocolValue(configArg);
    const host = hostValue(configArg);
    const endpoint = endpointValue(configArg.url || stringValue(configArg.metadata?.url));
    const port = configArg.port || endpoint?.port || (host ? kioskerDefaultPort : undefined);
    const appName = stringValue(status.appName) || stringValue(status.app_name);
    const appVersion = stringValue(status.appVersion) || stringValue(status.app_version);
    const osVersion = stringValue(status.osVersion) || stringValue(status.os_version);
    const batteryState = stringValue(status.batteryState) || stringValue(status.battery_state);

    return {
      device: {
        id: deviceId,
        name: configArg.name || `Kiosker ${shortId}`,
        manufacturer: 'Kiosker',
        model: stringValue(status.model) || kioskerProfile.model,
        serialNumber: deviceId,
        host,
        port,
        protocol,
        configurationUrl: host ? `${protocol}://${host}${port ? `:${port}` : ''}` : undefined,
        attributes: {
          appName,
          appVersion,
          osVersion,
          batteryState,
          lastUpdate: stringValue(status.date) || stringValue(status.lastUpdate) || stringValue(status.last_update),
        },
      },
      entities: kioskerEntities(status, blackout, screensaver),
      online: onlineArg,
      updatedAt: new Date().toISOString(),
      source: sourceArg,
      rawData: {
        status,
        blackout,
        screensaver,
      },
      capabilities: {
        localRead: true,
        localControl: false,
        liveControlRequiresExecutor: true,
      },
    };
  }
}

const kioskerEntities = (statusArg: IKioskerApiStatus, blackoutArg: IKioskerBlackoutState | null, screensaverArg: IKioskerScreensaverState | null): ISimpleLocalEntitySnapshot[] => [{
  id: 'batteryLevel',
  name: 'Battery',
  platform: 'sensor',
  state: numberOrNull(statusArg.batteryLevel ?? statusArg.battery_level),
  unit: '%',
  deviceClass: 'battery',
  stateClass: 'measurement',
}, {
  id: 'lastInteraction',
  name: 'Last interaction',
  platform: 'sensor',
  state: stringOrNull(statusArg.lastInteraction ?? statusArg.last_interaction),
  deviceClass: 'timestamp',
}, {
  id: 'lastMotion',
  name: 'Last motion',
  platform: 'sensor',
  state: stringOrNull(statusArg.lastMotion ?? statusArg.last_motion),
  deviceClass: 'timestamp',
}, {
  id: 'ambientLight',
  name: 'Ambient light',
  platform: 'sensor',
  state: numberOrNull(statusArg.ambientLight ?? statusArg.ambient_light),
  stateClass: 'measurement',
}, {
  id: 'blackoutState',
  name: 'Blackout state',
  platform: 'binary_sensor',
  state: Boolean(blackoutArg?.visible),
}, {
  id: 'screensaverState',
  name: 'Screensaver state',
  platform: 'binary_sensor',
  state: Boolean(screensaverArg?.visible),
}, {
  id: 'charging',
  name: 'Charging',
  platform: 'binary_sensor',
  state: ['charging', 'fully charged'].includes(String(statusArg.batteryState ?? statusArg.battery_state ?? '').toLowerCase()),
  deviceClass: 'battery_charging',
}];

const kioskerApiData = (valueArg: unknown): IKioskerApiData | undefined => {
  const rawObject = record(valueArg);
  if (!rawObject) {
    return undefined;
  }

  const status = record(rawObject.status) || (hasStatusFields(rawObject) ? rawObject : undefined);
  if (!status) {
    return undefined;
  }

  return {
    status: status as IKioskerApiStatus,
    blackout: record(rawObject.blackout) as IKioskerBlackoutState | undefined,
    screensaver: record(rawObject.screensaver) as IKioskerScreensaverState | undefined,
  };
};

const hasStatusFields = (valueArg: Record<string, unknown>): boolean => ['deviceId', 'device_id', 'batteryLevel', 'battery_level'].some((keyArg) => keyArg in valueArg);

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

const hostValue = (configArg: IKioskerConfig): string | undefined => {
  const endpoint = endpointValue(configArg.url || stringValue(configArg.metadata?.url));
  return endpoint?.host || stringValue(configArg.host);
};

const hostId = (configArg: IKioskerConfig): string | undefined => {
  const host = hostValue(configArg);
  const endpoint = endpointValue(configArg.url || stringValue(configArg.metadata?.url));
  return host ? `${host}:${configArg.port || endpoint?.port || kioskerDefaultPort}` : undefined;
};

const protocolValue = (configArg: IKioskerConfig): 'http' | 'https' => {
  if (configArg.protocol === 'http' || configArg.protocol === 'https') {
    return configArg.protocol;
  }
  if (typeof configArg.ssl === 'boolean') {
    return configArg.ssl ? 'https' : 'http';
  }
  if (typeof configArg.useTls === 'boolean') {
    return configArg.useTls ? 'https' : 'http';
  }
  const endpoint = endpointValue(configArg.url || stringValue(configArg.metadata?.url));
  return endpoint?.protocol || 'http';
};

const endpointValue = (valueArg: unknown): { protocol: 'http' | 'https'; host: string; port?: number } | undefined => {
  const value = stringValue(valueArg);
  if (!value || !/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
    return undefined;
  }
  try {
    const url = new URL(value);
    return url.protocol === 'https:' || url.protocol === 'http:' ? { protocol: url.protocol === 'https:' ? 'https' : 'http', host: url.hostname, port: url.port ? Number(url.port) : undefined } : undefined;
  } catch {
    return undefined;
  }
};

const numberOrNull = (valueArg: unknown): number | null => typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : null;
const stringOrNull = (valueArg: unknown): string | null => typeof valueArg === 'string' && valueArg.trim() ? valueArg : null;
const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
