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 { IGpsdConfig } from './gpsd.types.js';
import { gpsdDefaultName, gpsdDefaultPort, gpsdProfile } from './gpsd.types.js';

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

  public static toSnapshotFromRaw(configArg: IGpsdConfig, 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(gpsdProfile, snapshotArg);
  }

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

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

  private static normalizeRawData(configArg: IGpsdConfig, rawDataArg: unknown): unknown {
    if (!isRecord(rawDataArg) || ('device' in rawDataArg && 'entities' in rawDataArg)) {
      return rawDataArg;
    }

    const dataStream = recordValue(rawDataArg.data_stream) || recordValue(rawDataArg.dataStream) || recordValue(rawDataArg.tpv) || rawDataArg;
    const satellites = satellitesValue(dataStream.satellites ?? rawDataArg.satellites);
    const mode = modeValue(dataStream.mode);
    const lat = validState(dataStream.lat ?? dataStream.latitude);
    const lon = validState(dataStream.lon ?? dataStream.longitude);
    const hasGpsHints = Boolean(mode || lat !== undefined || lon !== undefined || validState(dataStream.alt ?? dataStream.elevation) !== undefined || satellites.length);
    if (!hasGpsHints) {
      return rawDataArg;
    }

    const host = configArg.host || stringValue(rawDataArg.host);
    const port = configArg.port || numberValue(rawDataArg.port) || (host ? gpsdDefaultPort : undefined);
    const name = configArg.name || stringValue(rawDataArg.name) || gpsdDefaultName;
    const uniqueBase = configArg.uniqueId || (host ? `${host}:${port || ''}` : undefined) || name;
    const entities: ISimpleLocalEntitySnapshot[] = [
      sensorEntity(uniqueBase, 'mode', 'Mode', mode, undefined, 'enum', undefined, { options: ['2d_fix', '3d_fix'], entityCategory: 'diagnostic' }),
      sensorEntity(uniqueBase, 'latitude', 'Latitude', lat, undefined, undefined, undefined, { entityCategory: 'diagnostic', entityRegistryEnabledDefault: false }),
      sensorEntity(uniqueBase, 'longitude', 'Longitude', lon, undefined, undefined, undefined, { entityCategory: 'diagnostic', entityRegistryEnabledDefault: false }),
      sensorEntity(uniqueBase, 'elevation', 'Elevation', validState(dataStream.alt ?? dataStream.elevation), 'm', 'distance', undefined, { entityCategory: 'diagnostic', entityRegistryEnabledDefault: false, suggestedDisplayPrecision: 2 }),
      sensorEntity(uniqueBase, 'time', 'Time', timestampValue(dataStream.time), undefined, 'timestamp', undefined, { entityCategory: 'diagnostic', entityRegistryEnabledDefault: false }),
      sensorEntity(uniqueBase, 'speed', 'Speed', validState(dataStream.speed), 'm/s', 'speed', undefined, { entityCategory: 'diagnostic', entityRegistryEnabledDefault: false, suggestedDisplayPrecision: 2 }),
      sensorEntity(uniqueBase, 'climb', 'Climb', validState(dataStream.climb), 'm/s', 'speed', undefined, { entityCategory: 'diagnostic', entityRegistryEnabledDefault: false, suggestedDisplayPrecision: 2 }),
      sensorEntity(uniqueBase, 'total_satellites', 'Total satellites', satellites.length || undefined, 'satellites', undefined, 'measurement', { entityCategory: 'diagnostic', entityRegistryEnabledDefault: false }),
      sensorEntity(uniqueBase, 'used_satellites', 'Used satellites', usedSatelliteCount(satellites), 'satellites', undefined, 'measurement', { entityCategory: 'diagnostic', entityRegistryEnabledDefault: false }),
    ];

    return {
      device: {
        id: uniqueBase,
        name,
        manufacturer: gpsdProfile.manufacturer,
        model: gpsdProfile.model,
        host,
        port,
        protocol: 'tcp',
      },
      entities,
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const sensorEntity = (uniqueBaseArg: string, idArg: string, nameArg: string, stateArg: unknown, unitArg?: string, deviceClassArg?: string, stateClassArg?: string, attributesArg?: Record<string, unknown>): ISimpleLocalEntitySnapshot => ({
  id: idArg,
  uniqueId: `${gpsdProfile.domain}_${SimpleLocalMapper.slug(uniqueBaseArg)}_${SimpleLocalMapper.slug(idArg)}`,
  name: nameArg,
  platform: 'sensor',
  state: stateArg ?? null,
  available: stateArg !== undefined,
  writable: false,
  unit: unitArg,
  deviceClass: deviceClassArg,
  stateClass: stateClassArg,
  attributes: attributesArg,
});

const modeValue = (valueArg: unknown): string | undefined => {
  const value = numberValue(valueArg);
  if (value === 2) return '2d_fix';
  if (value === 3) return '3d_fix';
  const text = stringValue(valueArg)?.toLowerCase();
  if (!text || text === 'n/a') return undefined;
  return text.replace(/\s+/g, '_');
};

const satellitesValue = (valueArg: unknown): Array<Record<string, unknown>> => Array.isArray(valueArg) ? valueArg.filter(isRecord) : [];
const usedSatelliteCount = (satellitesArg: Array<Record<string, unknown>>): number | undefined => satellitesArg.length ? satellitesArg.filter((satelliteArg) => booleanValue(satelliteArg.used) === true).length : undefined;

const timestampValue = (valueArg: unknown): string | undefined => {
  if (valueArg instanceof Date) return valueArg.toISOString();
  return stringValue(valueArg);
};

const validState = (valueArg: unknown): string | number | boolean | undefined => {
  if (valueArg === 'n/a' || valueArg === null || valueArg === undefined) return undefined;
  if (['string', 'number', 'boolean'].includes(typeof valueArg)) return valueArg as string | number | boolean;
  return undefined;
};

const recordValue = (valueArg: unknown): Record<string, unknown> | undefined => isRecord(valueArg) ? valueArg : undefined;
const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg));
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 => {
  if (typeof valueArg === 'boolean') return valueArg;
  if (typeof valueArg === 'number') return valueArg !== 0;
  const value = stringValue(valueArg)?.toLowerCase();
  if (!value) return undefined;
  if (['1', 'true', 'yes', 'used'].includes(value)) return true;
  if (['0', 'false', 'no'].includes(value)) return false;
  return undefined;
};
