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 { INukiApiData, INukiConfig } from './nuki.types.js';
import { nukiDefaultPort, nukiProfile } from './nuki.types.js';

const nukiLockStates: Record<number, string> = {
  0: 'uncalibrated',
  1: 'locked',
  2: 'unlocking',
  3: 'unlocked',
  4: 'locking',
  5: 'unlatched',
  6: 'unlocked_lock_n_go',
  7: 'unlatching',
  254: 'motor_blocked',
  255: 'undefined',
};

const nukiOpenerStates: Record<number, string> = {
  0: 'untrained',
  1: 'online',
  3: 'ring_to_open_active',
  5: 'open',
  7: 'opening',
  253: 'boot_run',
  255: 'undefined',
};

export class NukiMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<INukiConfig>, 'profile'>): ISimpleLocalSnapshot {
    const rawData = optionsArg.rawData ?? optionsArg.config.rawData;
    const apiData = this.apiData(rawData);
    if (apiData) {
      return this.snapshotFromApiData(optionsArg.config, apiData, optionsArg.online ?? true, optionsArg.source || 'http', optionsArg.error);
    }
    return SimpleLocalMapper.toSnapshot({ ...optionsArg, profile: nukiProfile });
  }

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

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

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

  private static snapshotFromApiData(configArg: INukiConfig, rawDataArg: INukiApiData, onlineArg: boolean, sourceArg: ISimpleLocalSnapshot['source'], errorArg?: string): ISimpleLocalSnapshot {
    const info = this.record(rawDataArg.info);
    const ids = this.record(info.ids);
    const versions = this.record(info.versions);
    const bridgeId = this.nukiId(this.numberValue(ids.hardwareId) ?? ids.hardwareId ?? configArg.uniqueId ?? configArg.deviceId);
    const host = this.endpointHost(configArg);
    const port = this.endpointPort(configArg);
    const devices = Array.isArray(rawDataArg.devices) ? rawDataArg.devices : [];
    const entities = devices.flatMap((deviceArg) => this.entitiesForDevice(deviceArg));

    return {
      device: {
        id: configArg.uniqueId || bridgeId || (host ? `${host}:${port || nukiDefaultPort}` : undefined) || nukiProfile.domain,
        name: configArg.name || (bridgeId ? `Nuki Bridge ${bridgeId}` : nukiProfile.defaultName || nukiProfile.displayName),
        manufacturer: 'Nuki Home Solutions GmbH',
        model: this.numberValue(info.bridgeType) === 1 ? 'Hardware Bridge' : 'Nuki Bridge',
        serialNumber: bridgeId,
        host,
        port,
        protocol: 'http',
        configurationUrl: host ? `http://${host}${port ? `:${port}` : ''}` : undefined,
        attributes: this.cleanAttributes({
          bridgeType: info.bridgeType,
          firmwareVersion: this.stringValue(versions.firmwareVersion),
          wifiFirmwareVersion: this.stringValue(versions.wifiFirmwareVersion),
          deviceCount: devices.length,
        }),
      },
      entities: entities.length ? entities : [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: onlineArg, available: true }],
      online: onlineArg,
      updatedAt: this.stringValue(rawDataArg.fetchedAt) || new Date().toISOString(),
      source: sourceArg,
      rawData: rawDataArg,
      error: errorArg,
      capabilities: {
        localRead: true,
        localControl: Boolean((configArg.host || configArg.url) && configArg.token),
        liveControlRequiresExecutor: false,
        encryptedTokenSupported: false,
      },
    };
  }

  private static entitiesForDevice(deviceArg: Record<string, unknown>): ISimpleLocalEntitySnapshot[] {
    const state = this.record(deviceArg.lastKnownState);
    const merged = { ...deviceArg, ...state };
    const nukiId = this.stringValue(merged.nukiId) || 'device';
    const deviceType = this.numberValue(merged.deviceType);
    const name = this.stringValue(merged.name) || `Nuki ${nukiId}`;
    const stateNumber = this.numberValue(merged.state);
    const stateName = this.stringValue(merged.stateName) || this.stateName(deviceType, stateNumber);
    const entities: ISimpleLocalEntitySnapshot[] = [{
      id: `device_${nukiId}`,
      name,
      platform: 'switch',
      state: this.primaryState(deviceType, stateNumber, this.numberValue(merged.mode)),
      available: ![0, 254, 255].includes(stateNumber ?? -1),
      writable: true,
      attributes: this.cleanAttributes({
        nukiId,
        deviceType,
        deviceModel: this.deviceModel(deviceType),
        mode: merged.mode,
        state: stateName,
        firmwareVersion: this.stringValue(merged.firmwareVersion),
      }),
    }];

    this.pushEntity(entities, `${nukiId}_battery_critical`, `${name} Battery Critical`, this.booleanValue(merged.batteryCritical), 'binary_sensor', undefined, { deviceClass: 'battery', entityCategory: 'diagnostic' });
    this.pushEntity(entities, `${nukiId}_battery_charging`, `${name} Battery Charging`, this.booleanValue(merged.batteryCharging), 'binary_sensor', undefined, { deviceClass: 'battery_charging', entityCategory: 'diagnostic' });
    this.pushEntity(entities, `${nukiId}_battery_level`, `${name} Battery Level`, this.numberValue(merged.batteryChargeState), 'sensor', '%', { deviceClass: 'battery', stateClass: 'measurement', entityCategory: 'diagnostic' });
    this.pushEntity(entities, `${nukiId}_door`, `${name} Door`, this.doorOpen(merged.doorsensorState), 'binary_sensor', undefined, { deviceClass: 'door', doorsensorState: merged.doorsensorState, doorsensorStateName: merged.doorsensorStateName });
    this.pushEntity(entities, `${nukiId}_ring_action`, `${name} Ring Action`, this.booleanValue(merged.ringactionState), 'binary_sensor', undefined, { ringactionTimestamp: merged.ringactionTimestamp });
    return entities;
  }

  private static apiData(valueArg: unknown): INukiApiData | undefined {
    const value = this.record(valueArg);
    return value && ('info' in value || 'devices' in value) ? value as INukiApiData : undefined;
  }

  private static pushEntity(entitiesArg: ISimpleLocalEntitySnapshot[], idArg: string, nameArg: string, valueArg: unknown, platformArg: ISimpleLocalEntitySnapshot['platform'], unitArg?: string, attributesArg?: Record<string, unknown>): void {
    if (valueArg === undefined) {
      return;
    }
    entitiesArg.push({
      id: idArg,
      name: nameArg,
      platform: platformArg,
      state: valueArg,
      available: true,
      writable: platformArg === 'switch',
      unit: unitArg,
      attributes: this.cleanAttributes(attributesArg),
    });
  }

  private static primaryState(deviceTypeArg: number | undefined, stateArg: number | undefined, modeArg: number | undefined): boolean | string {
    if (deviceTypeArg === 2) {
      return stateArg === 3 || modeArg === 3;
    }
    if (deviceTypeArg === 0 || deviceTypeArg === 3 || deviceTypeArg === 4) {
      return stateArg === 3 || stateArg === 5 || stateArg === 6 || stateArg === 7;
    }
    return this.stateName(deviceTypeArg, stateArg) || 'unknown';
  }

  private static stateName(deviceTypeArg: number | undefined, stateArg: number | undefined): string | undefined {
    if (stateArg === undefined) {
      return undefined;
    }
    return deviceTypeArg === 2 ? nukiOpenerStates[stateArg] || String(stateArg) : nukiLockStates[stateArg] || String(stateArg);
  }

  private static deviceModel(deviceTypeArg: number | undefined): string | undefined {
    if (deviceTypeArg === 0) {
      return 'Nuki Smart Lock 1.0/2.0';
    }
    if (deviceTypeArg === 2) {
      return 'Nuki Opener';
    }
    if (deviceTypeArg === 3) {
      return 'Nuki Smart Door';
    }
    if (deviceTypeArg === 4) {
      return 'Nuki Smart Lock 3.0 (Pro)';
    }
    return undefined;
  }

  private static doorOpen(valueArg: unknown): boolean | undefined {
    const value = this.numberValue(valueArg);
    if (value === undefined || value === 1 || value === 4) {
      return undefined;
    }
    return value === 3;
  }

  private static nukiId(valueArg: unknown): string | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg.toString(16).toUpperCase();
    }
    return this.stringValue(valueArg);
  }

  private static endpointHost(configArg: INukiConfig): string | undefined {
    if (configArg.host) {
      return this.urlHost(configArg.host) || configArg.host;
    }
    if (configArg.url) {
      return this.urlHost(configArg.url);
    }
    return undefined;
  }

  private static endpointPort(configArg: INukiConfig): number | undefined {
    if (configArg.port) {
      return configArg.port;
    }
    const source = configArg.url || configArg.host;
    if (!source) {
      return undefined;
    }
    try {
      const url = new URL(source.includes('://') ? source : `http://${source}`);
      return url.port ? Number(url.port) : nukiDefaultPort;
    } catch {
      return nukiDefaultPort;
    }
  }

  private static urlHost(valueArg: string): string | undefined {
    try {
      return new URL(valueArg.includes('://') ? valueArg : `http://${valueArg}`).hostname;
    } catch {
      return undefined;
    }
  }

  private static record(valueArg: unknown): Record<string, unknown> {
    return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : {};
  }

  private static stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg ? valueArg : typeof valueArg === 'number' && Number.isFinite(valueArg) ? String(valueArg) : undefined;
  }

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

  private static booleanValue(valueArg: unknown): boolean | undefined {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'number') {
      return valueArg !== 0;
    }
    if (typeof valueArg === 'string') {
      if (valueArg === '1' || valueArg.toLowerCase() === 'true') {
        return true;
      }
      if (valueArg === '0' || valueArg.toLowerCase() === 'false') {
        return false;
      }
    }
    return undefined;
  }

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