import {
  INodeType,
  INodeTypeDescription,
  IExecuteFunctions,
  NodeConnectionType,
} from 'n8n-workflow';

import mqtt from 'mqtt';

function resolveEnvVariables(str: string): string {
  return str.replace(/\$([a-zA-Z0-9_]+)/g, (_, v) => process.env[v] ?? '');
}

export class _MQTTOut implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'MQTTOut',
    name: 'mqttOut',
    group: ['output'],
    version: 1,
    description: 'Publish messages to MQTT broker',
    defaults: {
      name: 'MQTTOut',
    },
    inputs: [NodeConnectionType.Main],
    outputs: [NodeConnectionType.Main],
    credentials: [],
    properties: [
      {
        displayName: 'Host URL',
        name: 'host',
        type: 'string',
        default: 'tcp://$mqtthost',
      },
      {
        displayName: 'Port',
        name: 'port',
        type: 'string',
        default: '$mqttport',
      },
      {
        displayName: 'Username',
        name: 'username',
        type: 'string',
        default: '$mqttuser',
      },
      {
        displayName: 'Password',
        name: 'password',
        type: 'string',
        default: '$mqttpassword',
      },
      {
        displayName: 'QoS',
        name: 'qos',
        type: 'number',
        typeOptions: { minValue: 0, maxValue: 2 },
        default: 1,
      },
      {
        displayName: 'Retain',
        name: 'retain',
        type: 'boolean',
        default: false,
      },
      {
        displayName: 'Topic',
        name: 'topic',
        type: 'string',
        default: '101_AAA_0.12_UserToLocation_Out',
        displayOptions: {
          show: { useMetadataTopic: [false] },
        },
      },
      {
        displayName: 'Use topic from METADATA',
        name: 'useMetadataTopic',
        type: 'boolean',
        default: false,
      },
    ],
  };

  async execute(this: IExecuteFunctions) {
    const item = this.getInputData()[0]; // всегда один элемент

    // Формируем METADATA заново
    const nodeInfo = this.getNode();
    const workflowName = this.getWorkflow().name;

    item.json.METADATA = {
      from: nodeInfo.name,
      id: nodeInfo.name,
      workflow: workflowName,
      time: Date.now(),
    };

    // Получаем параметры
    const rawHost = this.getNodeParameter('host', 0) as string;
    const rawPort = this.getNodeParameter('port', 0) as string;
    const rawUsername = this.getNodeParameter('username', 0) as string;
    const rawPassword = this.getNodeParameter('password', 0) as string;

    const host = resolveEnvVariables(rawHost);
    const port = resolveEnvVariables(rawPort);
    const username = resolveEnvVariables(rawUsername);
    const password = resolveEnvVariables(rawPassword);

    const qosRaw = this.getNodeParameter('qos', 0) as number;
    const qos = (qosRaw === 0 || qosRaw === 1 || qosRaw === 2) ? qosRaw : 1;

    const retain = this.getNodeParameter('retain', 0) as boolean;
    const useMetadataTopic = this.getNodeParameter('useMetadataTopic', 0) as boolean;

    const url = `${host}:${port}`;

    const metadata = item.json?.METADATA as { mqtt_topic?: string };
    const topic = useMetadataTopic
      ? metadata?.mqtt_topic ?? 'undefined_topic'
      : (this.getNodeParameter('topic', 0) as string);

    const payload = JSON.stringify(item.json.DATA);

    const client = mqtt.connect(url, { username, password });

    await new Promise<void>((resolve, reject) => {
      client.on('connect', () => {
        client.publish(topic, payload, { qos, retain }, (err) => {
          client.end();
          if (err) return reject(err);
          resolve();
        });
      });

      client.on('error', (err) => {
        reject(err);
      });
    });

    return this.prepareOutputData([item]);
  }
}
