import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IIntegrationEvent, IServiceCallRequest } from '../../core/types.js';
import { rflinkDomain, rflinkSensorTypes } from './rflink.constants.js';
import type { IRflinkCommand, IRflinkEntity, IRflinkEvent, IRflinkSnapshot, TRflinkEntityPlatform } from './rflink.types.js';

export class RflinkMapper {
  public static toDevices(snapshotArg: IRflinkSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const gatewayId = this.gatewayDeviceId(snapshotArg);
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [{
      id: gatewayId,
      integrationDomain: rflinkDomain,
      name: snapshotArg.gateway.name || 'RFLink Gateway',
      protocol: snapshotArg.gateway.connectionType === 'tcp' ? 'http' : 'unknown',
      manufacturer: 'Nodo',
      model: snapshotArg.gateway.firmware || 'RFLink Gateway',
      online: snapshotArg.connected,
      features: [
        { id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
        { id: 'revision', capability: 'sensor', name: 'Revision', readable: true, writable: false },
      ],
      state: [
        { featureId: 'connectivity', value: snapshotArg.connected ? 'online' : 'offline', updatedAt },
        { featureId: 'revision', value: snapshotArg.gateway.revision || null, updatedAt },
      ],
      metadata: this.cleanAttributes({
        host: snapshotArg.gateway.host,
        port: snapshotArg.gateway.port,
        baudRate: snapshotArg.gateway.baudRate,
        hardware: snapshotArg.gateway.hardware,
        firmware: snapshotArg.gateway.firmware,
        version: snapshotArg.gateway.version,
        revision: snapshotArg.gateway.revision,
        ...snapshotArg.gateway.attributes,
      }),
    }];

    for (const entity of snapshotArg.entities) {
      devices.push({
        id: this.entityDeviceId(entity),
        integrationDomain: rflinkDomain,
        name: entity.name || entity.id,
        protocol: 'unknown',
        manufacturer: 'RFLink',
        model: this.modelForEntity(entity),
        online: this.available(snapshotArg, entity),
        features: this.featuresForEntity(entity),
        state: this.deviceStateForEntity(entity, updatedAt),
        metadata: this.cleanAttributes({
          rflinkId: entity.id,
          aliases: entity.aliases,
          groupAliases: entity.groupAliases,
          noGroupAliases: entity.noGroupAliases,
          viaDevice: gatewayId,
          platform: entity.platform,
          type: entity.type,
          sensorType: entity.sensorType,
          unitOfMeasurement: entity.unitOfMeasurement,
          deviceClass: entity.deviceClass,
          fireEvent: entity.fireEvent,
          signalRepetitions: entity.signalRepetitions,
          ...entity.metadata,
        }),
      });
    }

    return devices;
  }

  public static toEntities(snapshotArg: IRflinkSnapshot): IIntegrationEntity[] {
    const usedIds = new Map<string, number>();
    return snapshotArg.entities.map((entityArg) => {
      const platform = entityArg.platform;
      const name = entityArg.name || entityArg.id;
      const entityId = entityArg.entityId || this.uniqueEntityId(platform, name, usedIds);
      return {
        id: entityId,
        uniqueId: entityArg.uniqueId || `rflink_${this.slug(entityArg.id)}_${platform}`,
        integrationDomain: rflinkDomain,
        deviceId: this.entityDeviceId(entityArg),
        platform,
        name,
        state: this.stateForEntity(entityArg),
        attributes: this.cleanAttributes({
          rflinkId: entityArg.id,
          aliases: entityArg.aliases,
          groupAliases: entityArg.groupAliases,
          noGroupAliases: entityArg.noGroupAliases,
          command: entityArg.command,
          brightness: entityArg.brightness,
          sensorType: entityArg.sensorType,
          unitOfMeasurement: entityArg.unitOfMeasurement || this.sensorMetadata(entityArg).unit,
          deviceClass: entityArg.deviceClass || this.sensorMetadata(entityArg).deviceClass,
          stateClass: this.sensorMetadata(entityArg).stateClass,
          forceUpdate: entityArg.forceUpdate,
          offDelaySeconds: entityArg.offDelaySeconds,
          type: entityArg.type,
          assumedState: ['light', 'switch', 'cover'].includes(entityArg.platform),
          ...entityArg.attributes,
        }),
        available: this.available(snapshotArg, entityArg),
      };
    });
  }

  public static commandForService(snapshotArg: IRflinkSnapshot, requestArg: IServiceCallRequest): IRflinkCommand | undefined {
    if (requestArg.domain === rflinkDomain && ['refresh', 'reload'].includes(requestArg.service)) {
      return { type: 'refresh' };
    }

    if (requestArg.domain === rflinkDomain && requestArg.service === 'learn') {
      return this.learnCommand(snapshotArg, requestArg);
    }

    if (requestArg.domain === rflinkDomain && requestArg.service === 'send_command') {
      const rawLine = this.stringData(requestArg, 'rawLine') || this.stringData(requestArg, 'line');
      const deviceId = this.requestedRflinkDeviceId(snapshotArg, requestArg);
      const command = this.stringData(requestArg, 'command') || this.stringData(requestArg, 'rflinkCommand');
      if (rawLine) {
        return { type: 'send_command', deviceId, rawLine, rflinkCommand: command || 'raw' };
      }
      return deviceId && command ? { type: 'send_command', deviceId, rflinkCommand: command } : undefined;
    }

    const entity = this.findRequestedEntity(snapshotArg, requestArg);
    const deviceId = this.requestedRflinkDeviceId(snapshotArg, requestArg);

    if (['turn_on', 'turn_off'].includes(requestArg.service) && (requestArg.domain === 'light' || requestArg.domain === 'switch' || requestArg.domain === rflinkDomain)) {
      const targetEntity = entity || (deviceId ? this.entityByRflinkId(snapshotArg, deviceId) : undefined);
      if (requestArg.service === 'turn_on') {
        const dim = this.dimLevel(requestArg);
        if (dim !== undefined && (!targetEntity || targetEntity.platform === 'light')) {
          return { type: 'set_value', deviceId: targetEntity?.id || deviceId, entityId: requestArg.target.entityId, value: dim, brightness: this.rflinkToBrightness(dim), rflinkCommand: String(dim) };
        }
      }
      const rflinkCommand = targetEntity?.type === 'toggle' ? 'on' : requestArg.service === 'turn_on' ? 'on' : 'off';
      return targetEntity || deviceId ? { type: requestArg.service === 'turn_on' ? 'turn_on' : 'turn_off', deviceId: targetEntity?.id || deviceId, entityId: requestArg.target.entityId, rflinkCommand } : undefined;
    }

    if (requestArg.service === 'set_value' && (requestArg.domain === 'number' || requestArg.domain === 'light' || requestArg.domain === rflinkDomain)) {
      const dim = this.dimLevel(requestArg);
      return dim !== undefined && (entity || deviceId) ? { type: 'set_value', deviceId: entity?.id || deviceId, entityId: requestArg.target.entityId, value: dim, brightness: this.rflinkToBrightness(dim), rflinkCommand: String(dim) } : undefined;
    }

    if (requestArg.domain === 'cover' || requestArg.domain === rflinkDomain) {
      if (['open', 'open_cover'].includes(requestArg.service)) {
        return entity || deviceId ? { type: 'open_cover', deviceId: entity?.id || deviceId, entityId: requestArg.target.entityId, rflinkCommand: this.coverCommand(entity, 'open') } : undefined;
      }
      if (['close', 'close_cover'].includes(requestArg.service)) {
        return entity || deviceId ? { type: 'close_cover', deviceId: entity?.id || deviceId, entityId: requestArg.target.entityId, rflinkCommand: this.coverCommand(entity, 'close') } : undefined;
      }
      if (['stop', 'stop_cover'].includes(requestArg.service)) {
        return entity || deviceId ? { type: 'stop_cover', deviceId: entity?.id || deviceId, entityId: requestArg.target.entityId, rflinkCommand: 'STOP' } : undefined;
      }
    }

    return undefined;
  }

  public static toIntegrationEvent(eventArg: IRflinkEvent): IIntegrationEvent {
    const type = eventArg.type === 'command_failed' ? 'error' : eventArg.type === 'availability' ? 'availability_changed' : 'state_changed';
    return {
      type,
      integrationDomain: rflinkDomain,
      deviceId: eventArg.deviceId || (eventArg.id ? `rflink.device.${this.slug(eventArg.id)}` : undefined),
      entityId: eventArg.entityId,
      data: eventArg,
      timestamp: eventArg.timestamp,
    };
  }

  public static gatewayDeviceId(snapshotArg: IRflinkSnapshot): string {
    return `rflink.gateway.${this.slug(snapshotArg.gateway.id || snapshotArg.gateway.host || String(snapshotArg.gateway.port || rflinkDomain))}`;
  }

  public static entityDeviceId(entityArg: IRflinkEntity): string {
    return `rflink.${entityArg.platform}.${this.slug(entityArg.id)}`;
  }

  private static featuresForEntity(entityArg: IRflinkEntity): plugins.shxInterfaces.data.IDeviceDefinition['features'] {
    if (entityArg.platform === 'light') {
      const features: plugins.shxInterfaces.data.IDeviceFeature[] = [{ id: 'power', capability: 'light', name: 'Power', readable: true, writable: true }];
      if (entityArg.type === 'dimmable' || entityArg.type === 'hybrid') {
        features.push({ id: 'brightness', capability: 'light', name: 'Brightness', readable: true, writable: true, unit: '%' });
      }
      return features;
    }
    if (entityArg.platform === 'switch') {
      return [{ id: 'power', capability: 'switch', name: 'Power', readable: true, writable: true }];
    }
    if (entityArg.platform === 'binary_sensor') {
      return [{ id: 'state', capability: 'sensor', name: 'State', readable: true, writable: false }];
    }
    if (entityArg.platform === 'cover') {
      return [{ id: 'cover', capability: 'cover', name: 'Cover', readable: true, writable: true }];
    }
    return [{ id: 'value', capability: 'sensor', name: this.sensorMetadata(entityArg).name || 'Value', readable: true, writable: false, unit: entityArg.unitOfMeasurement || this.sensorMetadata(entityArg).unit }];
  }

  private static deviceStateForEntity(entityArg: IRflinkEntity, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition['state'] {
    if (entityArg.platform === 'light') {
      const state: plugins.shxInterfaces.data.IDeviceState[] = [{ featureId: 'power', value: this.onState(entityArg), updatedAt: updatedAtArg }];
      if (entityArg.type === 'dimmable' || entityArg.type === 'hybrid') {
        state.push({ featureId: 'brightness', value: entityArg.brightness ?? null, updatedAt: updatedAtArg });
      }
      return state;
    }
    if (entityArg.platform === 'switch' || entityArg.platform === 'binary_sensor') {
      return [{ featureId: entityArg.platform === 'switch' ? 'power' : 'state', value: this.onState(entityArg), updatedAt: updatedAtArg }];
    }
    if (entityArg.platform === 'cover') {
      return [{ featureId: 'cover', value: this.coverState(entityArg), updatedAt: updatedAtArg }];
    }
    return [{ featureId: 'value', value: this.deviceStateValue(this.stateForEntity(entityArg)), updatedAt: updatedAtArg }];
  }

  private static stateForEntity(entityArg: IRflinkEntity): unknown {
    if (entityArg.platform === 'sensor') {
      return entityArg.value ?? entityArg.state ?? null;
    }
    if (entityArg.platform === 'binary_sensor' || entityArg.platform === 'light' || entityArg.platform === 'switch') {
      const on = this.onState(entityArg);
      return on === null ? 'unknown' : on ? 'on' : 'off';
    }
    if (entityArg.platform === 'cover') {
      return this.coverState(entityArg);
    }
    return entityArg.state ?? null;
  }

  private static onState(entityArg: IRflinkEntity): boolean | null {
    const state = String(entityArg.state || entityArg.command || '').toLowerCase();
    if (['on', 'allon', 'up', 'open'].includes(state)) {
      return true;
    }
    if (['off', 'alloff', 'down', 'closed', 'close'].includes(state)) {
      return false;
    }
    return null;
  }

  private static coverState(entityArg: IRflinkEntity): string {
    const state = String(entityArg.state || entityArg.command || '').toLowerCase();
    if (['on', 'allon', 'up', 'open', 'opened'].includes(state)) {
      return 'open';
    }
    if (['off', 'alloff', 'down', 'close', 'closed'].includes(state)) {
      return 'closed';
    }
    if (state === 'stop' || state === 'stopped') {
      return 'stopped';
    }
    return 'unknown';
  }

  private static modelForEntity(entityArg: IRflinkEntity): string {
    if (entityArg.platform === 'light') {
      return `RFLink ${entityArg.type || 'switchable'} light`;
    }
    if (entityArg.platform === 'cover') {
      return `RFLink ${entityArg.type || 'standard'} cover`;
    }
    return `RFLink ${entityArg.platform.replace('_', ' ')}`;
  }

  private static learnCommand(snapshotArg: IRflinkSnapshot, requestArg: IServiceCallRequest): IRflinkCommand | undefined {
    const rawLine = this.stringData(requestArg, 'rawLine') || this.stringData(requestArg, 'line');
    const command = this.stringData(requestArg, 'command') || 'PAIR';
    if (rawLine) {
      return { type: 'learn', rawLine, rflinkCommand: command };
    }
    const protocol = this.stringData(requestArg, 'protocol');
    const address = this.stringData(requestArg, 'address') || this.stringData(requestArg, 'id');
    const rollingCode = this.stringData(requestArg, 'rollingCode') || this.stringData(requestArg, 'rolling_code');
    const button = this.stringData(requestArg, 'button') || this.stringData(requestArg, 'switch');
    if (protocol && address && rollingCode && button) {
      return { type: 'learn', rawLine: `10;${protocol};${address};${rollingCode};${button};${command};`, rflinkCommand: command };
    }
    const deviceId = this.requestedRflinkDeviceId(snapshotArg, requestArg);
    return deviceId ? { type: 'learn', deviceId, rflinkCommand: command } : undefined;
  }

  private static requestedRflinkDeviceId(snapshotArg: IRflinkSnapshot, requestArg: IServiceCallRequest): string | undefined {
    const dataDeviceId = this.stringData(requestArg, 'deviceId') || this.stringData(requestArg, 'device_id');
    if (dataDeviceId) {
      return dataDeviceId;
    }
    return this.findRequestedEntity(snapshotArg, requestArg)?.id;
  }

  private static findRequestedEntity(snapshotArg: IRflinkSnapshot, requestArg: IServiceCallRequest): IRflinkEntity | undefined {
    const entityId = requestArg.target.entityId || this.stringData(requestArg, 'entityId') || this.stringData(requestArg, 'entity_id');
    const deviceId = requestArg.target.deviceId || this.stringData(requestArg, 'targetDeviceId');
    const coreEntities = this.toEntities(snapshotArg);
    if (entityId) {
      const coreEntity = coreEntities.find((entityArg) => entityArg.id === entityId || entityArg.uniqueId === entityId);
      return coreEntity ? snapshotArg.entities.find((entityArg) => this.entityDeviceId(entityArg) === coreEntity.deviceId) : snapshotArg.entities.find((entityArg) => entityArg.id === entityId || entityArg.entityId === entityId || entityArg.uniqueId === entityId);
    }
    if (deviceId) {
      return snapshotArg.entities.find((entityArg) => this.entityDeviceId(entityArg) === deviceId || entityArg.id === deviceId);
    }
    return undefined;
  }

  private static entityByRflinkId(snapshotArg: IRflinkSnapshot, deviceIdArg: string): IRflinkEntity | undefined {
    return snapshotArg.entities.find((entityArg) => entityArg.id === deviceIdArg || entityArg.aliases?.includes(deviceIdArg));
  }

  private static coverCommand(entityArg: IRflinkEntity | undefined, directionArg: 'open' | 'close'): string {
    const inverted = entityArg?.type === 'inverted';
    if (directionArg === 'open') {
      return inverted ? 'DOWN' : 'UP';
    }
    return inverted ? 'UP' : 'DOWN';
  }

  private static dimLevel(requestArg: IServiceCallRequest): number | undefined {
    const brightness = this.numberData(requestArg, 'brightness');
    if (brightness !== undefined) {
      return Math.max(0, Math.min(15, Math.floor(Math.max(0, Math.min(255, brightness)) / 17)));
    }
    const brightnessPct = this.numberData(requestArg, 'brightness_pct') ?? this.numberData(requestArg, 'brightnessPercent');
    if (brightnessPct !== undefined) {
      return Math.max(0, Math.min(15, Math.floor(Math.max(0, Math.min(100, brightnessPct)) * 255 / 100 / 17)));
    }
    const value = this.numberData(requestArg, 'value');
    return value !== undefined ? Math.max(0, Math.min(15, Math.round(value))) : undefined;
  }

  private static rflinkToBrightness(valueArg: number): number {
    return Math.max(0, Math.min(255, valueArg * 17));
  }

  private static sensorMetadata(entityArg: IRflinkEntity): { name?: string; unit?: string; deviceClass?: string; stateClass?: string } {
    return entityArg.sensorType ? rflinkSensorTypes[entityArg.sensorType] || {} : {};
  }

  private static available(snapshotArg: IRflinkSnapshot, entityArg: IRflinkEntity): boolean {
    return entityArg.available === true || snapshotArg.connected && entityArg.available !== false;
  }

  private static uniqueEntityId(platformArg: TRflinkEntityPlatform, nameArg: string, usedIdsArg: Map<string, number>): string {
    const base = `${platformArg}.${this.slug(nameArg) || platformArg}`;
    const seen = usedIdsArg.get(base) || 0;
    usedIdsArg.set(base, seen + 1);
    return seen ? `${base}_${seen + 1}` : base;
  }

  private static deviceStateValue(valueArg: unknown): plugins.shxInterfaces.data.TDeviceStateValue {
    if (valueArg === undefined) {
      return null;
    }
    if (typeof valueArg === 'string' || typeof valueArg === 'number' || typeof valueArg === 'boolean' || valueArg === null) {
      return valueArg;
    }
    if (typeof valueArg === 'object' && !Array.isArray(valueArg)) {
      return valueArg as Record<string, unknown>;
    }
    return JSON.stringify(valueArg);
  }

  private static cleanAttributes(attributesArg: Record<string, unknown>): Record<string, unknown> {
    return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  }

  private static stringData(requestArg: IServiceCallRequest, keyArg: string): string | undefined {
    const value = requestArg.data?.[keyArg];
    return typeof value === 'string' && value.trim() ? value.trim() : undefined;
  }

  private static numberData(requestArg: IServiceCallRequest, keyArg: string): number | undefined {
    const value = requestArg.data?.[keyArg];
    return typeof value === 'number' && Number.isFinite(value) ? value : typeof value === 'string' && value.trim() && Number.isFinite(Number(value)) ? Number(value) : undefined;
  }

  private static slug(valueArg: string): string {
    return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'rflink';
  }
}
