import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IIntegrationEvent, IServiceCallRequest } from '../../core/types.js';
import { dlinkAttribution, dlinkDefaultName, dlinkDefaultPort, dlinkDefaultUsername, dlinkDomain, dlinkManufacturer } from './dlink.constants.js';
import type { IDlinkCommand, IDlinkConfig, IDlinkEvent, IDlinkSnapshot, TDlinkPowerState } from './dlink.types.js';

export class DlinkMapper {
  public static toSnapshot(configArg: IDlinkConfig, overridesArg: Partial<IDlinkSnapshot> = {}, eventsArg: IDlinkEvent[] = []): IDlinkSnapshot {
    const source = configArg.snapshot;
    const sourceSwitch = source?.switch;
    const sourceDevice = source?.device;
    const updatedAt = overridesArg.updatedAt || source?.updatedAt || new Date().toISOString();
    const host = configArg.host || configArg.ipAddress || configArg.address || configArg.ip || sourceDevice?.host;
    const port = configArg.port || sourceDevice?.port || dlinkDefaultPort;
    const state = this.normalizeState(overridesArg.switch?.state ?? configArg.state ?? sourceSwitch?.state);
    const hasRepresentedState = state !== 'unknown';
    const available = overridesArg.switch?.available ?? sourceSwitch?.available ?? configArg.connected ?? hasRepresentedState;
    const connected = overridesArg.connected ?? configArg.connected ?? source?.connected ?? available;
    const temperatureRaw = this.stringValue(overridesArg.switch?.temperatureRaw) ?? this.stringValue(configArg.temperature) ?? sourceSwitch?.temperatureRaw;
    const currentConsumptionRaw = this.stringValue(overridesArg.switch?.currentConsumptionRaw) ?? this.stringValue(configArg.currentConsumption) ?? sourceSwitch?.currentConsumptionRaw;
    const totalConsumptionRaw = this.stringValue(overridesArg.switch?.totalConsumptionRaw) ?? this.stringValue(configArg.totalConsumption) ?? sourceSwitch?.totalConsumptionRaw;
    const deviceId = configArg.id || sourceDevice?.id || configArg.macAddress || configArg.mac || host || 'dlink-smart-plug';

    return {
      connected,
      device: {
        id: deviceId,
        name: overridesArg.device?.name || configArg.name || sourceDevice?.name || dlinkDefaultName,
        host,
        port,
        macAddress: configArg.macAddress || configArg.mac || sourceDevice?.macAddress,
        manufacturer: configArg.manufacturer || sourceDevice?.manufacturer || dlinkManufacturer,
        modelName: overridesArg.device?.modelName || configArg.modelName || configArg.model || sourceDevice?.modelName || dlinkDefaultName,
        useLegacyProtocol: overridesArg.device?.useLegacyProtocol ?? configArg.useLegacyProtocol ?? configArg.use_legacy_protocol ?? sourceDevice?.useLegacyProtocol ?? false,
        authenticated: overridesArg.device?.authenticated ?? sourceDevice?.authenticated,
        available: overridesArg.device?.available ?? connected,
        metadata: this.cleanAttributes({
          ...sourceDevice?.metadata,
          ...configArg.metadata,
          ...overridesArg.device?.metadata,
          username: configArg.username || dlinkDefaultUsername,
        }),
      },
      switch: {
        state,
        available,
        temperature: this.numberValue(overridesArg.switch?.temperature) ?? this.numberValue(configArg.temperature) ?? sourceSwitch?.temperature ?? this.numberValue(temperatureRaw) ?? null,
        temperatureRaw,
        currentConsumption: this.numberValue(overridesArg.switch?.currentConsumption) ?? this.numberValue(configArg.currentConsumption) ?? sourceSwitch?.currentConsumption ?? this.numberValue(currentConsumptionRaw) ?? null,
        currentConsumptionRaw,
        totalConsumption: this.numberValue(overridesArg.switch?.totalConsumption) ?? this.numberValue(configArg.totalConsumption) ?? sourceSwitch?.totalConsumption ?? this.numberValue(totalConsumptionRaw) ?? null,
        totalConsumptionRaw,
        attributes: this.cleanAttributes({
          ...sourceSwitch?.attributes,
          ...overridesArg.switch?.attributes,
        }),
      },
      updatedAt,
      events: [...(source?.events || []), ...eventsArg, ...(overridesArg.events || [])],
      metadata: this.cleanAttributes({
        ...source?.metadata,
        ...configArg.metadata,
        ...overridesArg.metadata,
        nativeHttpImplemented: true,
        nativeHttpEnabled: configArg.nativeHttpEnabled !== false,
        commandExecutorConfigured: Boolean(configArg.commandExecutor || configArg.transport?.execute),
        nativeClientConfigured: Boolean(configArg.nativeClient),
      }),
    };
  }

  public static mergeSnapshot(baseArg: IDlinkSnapshot, patchArg: Partial<IDlinkSnapshot>): IDlinkSnapshot {
    return {
      ...baseArg,
      ...patchArg,
      device: {
        ...baseArg.device,
        ...patchArg.device,
        metadata: this.cleanAttributes({ ...baseArg.device.metadata, ...patchArg.device?.metadata }),
      },
      switch: {
        ...baseArg.switch,
        ...patchArg.switch,
        state: this.normalizeState(patchArg.switch?.state ?? baseArg.switch.state),
        attributes: this.cleanAttributes({ ...baseArg.switch.attributes, ...patchArg.switch?.attributes }),
      },
      events: [...baseArg.events, ...(patchArg.events || [])],
      metadata: this.cleanAttributes({ ...baseArg.metadata, ...patchArg.metadata }),
      updatedAt: patchArg.updatedAt || baseArg.updatedAt || new Date().toISOString(),
    };
  }

  public static toDevices(snapshotArg: IDlinkSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'power', capability: 'switch', name: 'Power', readable: true, writable: true },
      { id: 'temperature', capability: 'sensor', name: 'Temperature', readable: true, writable: false, unit: 'C' },
      { id: 'current_consumption', capability: 'energy', name: 'Current consumption', readable: true, writable: false, unit: 'W' },
      { id: 'total_consumption', capability: 'energy', name: 'Total consumption', readable: true, writable: false },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'power', value: snapshotArg.switch.state === 'unknown' ? null : snapshotArg.switch.state === 'ON', updatedAt },
      { featureId: 'temperature', value: snapshotArg.switch.temperature ?? null, updatedAt },
      { featureId: 'current_consumption', value: snapshotArg.switch.currentConsumption ?? null, updatedAt },
      { featureId: 'total_consumption', value: snapshotArg.switch.totalConsumption ?? null, updatedAt },
    ];

    return [{
      id: this.deviceId(snapshotArg),
      integrationDomain: dlinkDomain,
      name: snapshotArg.device.name || dlinkDefaultName,
      protocol: 'http',
      manufacturer: snapshotArg.device.manufacturer || dlinkManufacturer,
      model: snapshotArg.device.modelName || dlinkDefaultName,
      online: snapshotArg.connected && snapshotArg.switch.available,
      features,
      state,
      metadata: this.cleanAttributes({
        attribution: dlinkAttribution,
        host: snapshotArg.device.host,
        port: snapshotArg.device.port,
        macAddress: snapshotArg.device.macAddress,
        useLegacyProtocol: snapshotArg.device.useLegacyProtocol,
        authenticated: snapshotArg.device.authenticated,
        ...snapshotArg.metadata,
      }),
    }];
  }

  public static toEntities(snapshotArg: IDlinkSnapshot): IIntegrationEntity[] {
    return [this.switchEntity(snapshotArg)];
  }

  public static commandForService(snapshotArg: IDlinkSnapshot, requestArg: IServiceCallRequest): IDlinkCommand | undefined {
    const requestedState = this.requestedState(requestArg);
    if (!requestedState) {
      return undefined;
    }
    const targetMatches = this.targetMatches(snapshotArg, requestArg);
    if (!targetMatches) {
      return undefined;
    }
    const entity = this.switchEntity(snapshotArg);
    const type: IDlinkCommand['type'] = requestArg.service === 'turn_on' ? 'switch_turn_on' : requestArg.service === 'turn_off' ? 'switch_turn_off' : 'set_state';
    return {
      type,
      method: 'hnap.SetSocketSettings',
      state: requestedState,
      service: requestArg.service,
      target: requestArg.target,
      data: requestArg.data,
      deviceId: entity.deviceId,
      entityId: entity.id,
      host: snapshotArg.device.host,
      transmitted: false,
      metadata: this.cleanAttributes({
        useLegacyProtocol: snapshotArg.device.useLegacyProtocol,
        modelName: snapshotArg.device.modelName,
      }),
    };
  }

  public static toIntegrationEvent(eventArg: IDlinkEvent): IIntegrationEvent {
    return {
      type: eventArg.type === 'command_failed' ? 'error' : 'state_changed',
      integrationDomain: dlinkDomain,
      deviceId: eventArg.deviceId,
      entityId: eventArg.entityId,
      data: eventArg,
      timestamp: eventArg.timestamp || Date.now(),
    };
  }

  public static deviceId(snapshotArg: IDlinkSnapshot): string {
    return `dlink.device.${this.slug(snapshotArg.device.id || snapshotArg.device.macAddress || snapshotArg.device.host || snapshotArg.device.name || 'smart_plug')}`;
  }

  public static switchEntityId(snapshotArg: IDlinkSnapshot): string {
    return `switch.${this.slug(snapshotArg.device.name || snapshotArg.device.id || snapshotArg.device.host || 'dlink_smart_plug')}`;
  }

  public static normalizeState(valueArg: unknown): TDlinkPowerState {
    if (typeof valueArg === 'boolean') {
      return valueArg ? 'ON' : 'OFF';
    }
    if (typeof valueArg !== 'string') {
      return 'unknown';
    }
    const normalized = valueArg.trim().toLowerCase();
    if (normalized === 'on' || normalized === 'true' || normalized === '1') {
      return 'ON';
    }
    if (normalized === 'off' || normalized === 'false' || normalized === '0') {
      return 'OFF';
    }
    return 'unknown';
  }

  public static numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg === 'string' && valueArg.trim() && valueArg.trim().toLowerCase() !== 'n/a') {
      const parsed = Number(valueArg);
      return Number.isFinite(parsed) ? parsed : undefined;
    }
    return undefined;
  }

  public static slug(valueArg: unknown): string {
    const slug = String(valueArg || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
    return slug || 'dlink';
  }

  public static cleanAttributes<TValue extends Record<string, unknown>>(valueArg: TValue): TValue {
    const result: Record<string, unknown> = {};
    for (const [key, value] of Object.entries(valueArg)) {
      if (value !== undefined) {
        result[key] = value;
      }
    }
    return result as TValue;
  }

  private static switchEntity(snapshotArg: IDlinkSnapshot): IIntegrationEntity {
    return {
      id: this.switchEntityId(snapshotArg),
      uniqueId: `dlink_${this.slug(snapshotArg.device.id || snapshotArg.device.macAddress || snapshotArg.device.host || snapshotArg.device.name)}_switch`,
      integrationDomain: dlinkDomain,
      deviceId: this.deviceId(snapshotArg),
      platform: 'switch',
      name: snapshotArg.device.name || dlinkDefaultName,
      state: snapshotArg.switch.state === 'unknown' ? 'unknown' : snapshotArg.switch.state === 'ON' ? 'on' : 'off',
      available: snapshotArg.connected && snapshotArg.switch.available,
      attributes: this.cleanAttributes({
        attribution: dlinkAttribution,
        nativeType: 'smart_plug_switch',
        writable: true,
        temperature: snapshotArg.switch.temperature ?? null,
        total_consumption: snapshotArg.switch.totalConsumption ?? null,
        current_consumption: snapshotArg.switch.currentConsumption ?? null,
        host: snapshotArg.device.host,
        port: snapshotArg.device.port,
        modelName: snapshotArg.device.modelName,
        manufacturer: snapshotArg.device.manufacturer,
        useLegacyProtocol: snapshotArg.device.useLegacyProtocol,
        ...snapshotArg.switch.attributes,
      }),
    };
  }

  private static requestedState(requestArg: IServiceCallRequest): Exclude<TDlinkPowerState, 'unknown'> | undefined {
    if (requestArg.domain === 'switch' && requestArg.service === 'turn_on') {
      return 'ON';
    }
    if (requestArg.domain === 'switch' && requestArg.service === 'turn_off') {
      return 'OFF';
    }
    if (requestArg.domain === dlinkDomain && requestArg.service === 'set_state') {
      const state = this.normalizeState(requestArg.data?.state ?? requestArg.data?.power);
      return state === 'unknown' ? undefined : state;
    }
    if (requestArg.domain === dlinkDomain && requestArg.service === 'turn_on') {
      return 'ON';
    }
    if (requestArg.domain === dlinkDomain && requestArg.service === 'turn_off') {
      return 'OFF';
    }
    return undefined;
  }

  private static targetMatches(snapshotArg: IDlinkSnapshot, requestArg: IServiceCallRequest): boolean {
    const target = requestArg.target || {};
    if (!target.entityId && !target.deviceId) {
      return true;
    }
    return target.entityId === this.switchEntityId(snapshotArg) || target.deviceId === this.deviceId(snapshotArg);
  }

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