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 { INaswebConfig, TNaswebRawData } from './nasweb.types.js';
import { naswebDefaultName, naswebDefaultPort, naswebProfile } from './nasweb.types.js';

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

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

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

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

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

  public static offlineSnapshot(configArg: INaswebConfig, errorArg: string): ISimpleLocalSnapshot {
    return this.toSnapshot({ config: configArg, online: false, source: 'runtime', error: errorArg });
  }

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

    const deviceInfo = objectValue(rawObject.deviceInfo) || objectValue(rawObject.info);
    const status = objectValue(rawObject.status) || statusObject(rawObject);
    if (!deviceInfo && !status) {
      return parsedRawData;
    }

    const endpoint = endpointFromConfig(configArg);
    const serial = stringValue(deviceInfo?.devSN ?? deviceInfo?.device_serial ?? status?.device_serial)?.replace(/-/g, '');
    const name = configArg.name || stringValue(deviceInfo?.devName ?? deviceInfo?.device_name) || naswebDefaultName;
    const entities = status ? entitiesFromStatus(status) : [];

    return {
      device: {
        id: configArg.uniqueId || configArg.deviceId || serial || (endpoint ? `${endpoint.host}:${endpoint.port}` : undefined) || naswebDefaultName,
        name,
        manufacturer: 'chomtech.pl',
        model: 'NASweb',
        serialNumber: serial,
        host: endpoint?.host,
        port: endpoint?.port,
        protocol: endpoint?.useTls === false ? 'http' : 'https',
        configurationUrl: endpoint ? `${endpoint.useTls === false ? 'http' : 'https'}://${endpoint.host}${endpoint.port === (endpoint.useTls === false ? 80 : 443) ? '' : `:${endpoint.port}`}/page` : undefined,
      },
      entities,
      deviceInfo,
      status,
      subscription: boolValue(rawObject.subscription),
      fetchedAt: stringValue(rawObject.fetchedAt) || new Date().toISOString(),
    };
  }
}

const entitiesFromStatus = (statusArg: Record<string, unknown>): ISimpleLocalEntitySnapshot[] => {
  const entities: ISimpleLocalEntitySnapshot[] = [];
  for (const output of objectArray(statusArg.outputs)) {
    const index = numberValue(output.index);
    if (index === undefined) {
      continue;
    }
    entities.push({
      id: `output_${index}`,
      name: `Output ${index}`,
      platform: 'switch',
      state: boolValue(output.status),
      writable: true,
      available: output.status !== undefined && output.status !== null,
      attributes: cleanAttributes({ index }),
    });
  }
  for (const input of objectArray(statusArg.inputs)) {
    const index = numberValue(input.index);
    if (index === undefined) {
      continue;
    }
    entities.push({
      id: `input_${index}`,
      name: `Input ${index}`,
      platform: 'sensor',
      state: stringValue(input.status) || null,
      available: input.status !== undefined && input.status !== null,
      attributes: cleanAttributes({ index }),
    });
  }
  for (const zone of objectArray(statusArg.zones)) {
    const index = numberValue(zone.index);
    if (index === undefined) {
      continue;
    }
    entities.push({
      id: `zone_${index}`,
      name: stringValue(zone.name) || `Zone ${index}`,
      platform: 'binary_sensor',
      state: stringValue(zone.status) || null,
      writable: true,
      available: zone.status !== undefined && zone.status !== null,
      attributes: cleanAttributes({ index, passType: numberValue(zone.pass_type) }),
    });
  }
  const temp = numberValue(statusArg.device_temp_env);
  if (temp !== undefined) {
    entities.push({
      id: 'temp_sensor',
      name: 'Temperature',
      platform: 'sensor',
      state: temp,
      unit: 'C',
      deviceClass: 'temperature',
      stateClass: 'measurement',
    });
  }
  const thermostat = objectValue(statusArg.thermostat);
  if (thermostat && Object.keys(thermostat).length > 6) {
    entities.push({
      id: 'thermostat',
      name: stringValue(thermostat.name) || 'Thermostat',
      platform: 'climate',
      state: hvacMode(thermostat),
      writable: true,
      attributes: cleanAttributes({
        currentTemperature: numberValue(thermostat.temp_env),
        targetTemperatureLow: numberValue(thermostat.range_from),
        targetTemperatureHigh: numberValue(thermostat.range_to),
        enabledBelowOutput: numberValue(thermostat.below_check) === 1,
        enabledInRangeOutput: numberValue(thermostat.idle_output) !== undefined,
        enabledAboveOutput: numberValue(thermostat.above_check) === 1,
      }),
    });
  }
  if (!entities.length) {
    entities.push({ id: 'online', name: 'Online', platform: 'binary_sensor', state: true });
  }
  return entities;
};

const hvacMode = (thermostatArg: Record<string, unknown>): string => {
  const cooling = numberValue(thermostatArg.above_check) === 1;
  const heating = numberValue(thermostatArg.below_check) === 1;
  if (cooling && heating) {
    return 'heat_cool';
  }
  if (cooling) {
    return 'cool';
  }
  if (heating) {
    return 'heat';
  }
  if (numberValue(thermostatArg.idle_output) !== undefined) {
    return 'fan_only';
  }
  return 'off';
};

const statusObject = (rawObjectArg: Record<string, unknown>): Record<string, unknown> | undefined => {
  return rawObjectArg.type === 'status_update' || rawObjectArg.outputs || rawObjectArg.inputs || rawObjectArg.zones || rawObjectArg.thermostat ? rawObjectArg : undefined;
};

const endpointFromConfig = (configArg: INaswebConfig): { host: string; port: number; useTls: boolean } | undefined => {
  const rawUrl = stringValue(configArg.url) || stringValue(configArg.host);
  if (!rawUrl) {
    return undefined;
  }
  try {
    const defaultProtocol = configArg.useTls === false ? 'http' : 'https';
    const url = new URL(rawUrl.includes('://') ? rawUrl : `${defaultProtocol}://${rawUrl}`);
    const useTls = url.protocol === 'https:';
    return { host: url.hostname, port: configArg.port || numberValue(url.port) || (useTls ? naswebDefaultPort : 80), useTls };
  } catch {
    return { host: rawUrl, port: configArg.port || (configArg.useTls === false ? 80 : naswebDefaultPort), useTls: configArg.useTls !== false };
  }
};

const objectArray = (valueArg: unknown): Array<Record<string, unknown>> => {
  return Array.isArray(valueArg) ? valueArg.filter((itemArg): itemArg is Record<string, unknown> => Boolean(itemArg && typeof itemArg === 'object' && !Array.isArray(itemArg))) : [];
};

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 unknown;
  } catch {
    return valueArg;
  }
};

const stringValue = (valueArg: unknown): string | undefined => {
  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;
  }
  const value = stringValue(valueArg)?.toLowerCase();
  if (value === 'true' || value === '1' || value === 'on') {
    return true;
  }
  if (value === 'false' || value === '0' || value === 'off') {
    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 !== ''));
};
