const net = require("net");
const tls = require("tls");
const iconv = require("iconv-lite");
const { EventEmitter } = require("events");

class RouterOSClient extends EventEmitter {
  constructor(options = {}) {
    super();
    this.host = options.host || "192.168.88.1";
    this.port = options.port || 8728;
    this.username = options.username || "admin";
    this.password = options.password || "";
    this.timeout = options.timeout || 10000;
    this.tls = options.tls || false;

    this.socket = null;
    this.connected = false;
    this.buffer = Buffer.alloc(0);
    this.currentRequest = null;
    this.debug = options.debug || false;
    this.sentences = [];
  }

  connect() {
    return new Promise((resolve, reject) => {
      if (this.socket) {
        this.socket.destroy();
        this.socket = null;
      }

      const timeoutId = setTimeout(() => {
        if (this.socket) {
          this.socket.destroy();
          this.socket = null;
        }
        reject(new Error("Connection timeout"));
      }, this.timeout);

      if (this.tls) {
        this.socket = tls.connect({
          host: this.host,
          port: this.port || 8729,
          rejectUnauthorized: false,
        });
      } else {
        this.socket = net.createConnection({
          host: this.host,
          port: this.port || 8728,
        });
      }

      this.socket.on("connect", () => {
        clearTimeout(timeoutId);
        if (this.debug) console.log("Socket connected");
        this._login().then(resolve).catch(reject);
      });

      this.socket.on("data", (data) => {
        this.buffer = Buffer.concat([this.buffer, data]);
        this._parseResponse();
      });

      this.socket.on("error", (err) => {
        clearTimeout(timeoutId);
        this.emit("error", err);
        reject(err);
      });

      this.socket.on("close", () => {
        this.connected = false;
        this.emit("close");
      });
    });
  }

  _login() {
    return new Promise((resolve, reject) => {
      this.sentences = []; // Clear any previous data

      this._sendCommand(["/login"], (response) => {
        if (response.error) {
          reject(new Error(response.error));
          return;
        }

        if (this.debug) console.log("Login response:", response);

        // Modern login method (v6.43+)
        this._sendCommand(
          ["/login", `=name=${this.username}`, `=password=${this.password}`],
          (loginResponse) => {
            if (this.debug) console.log("Auth response:", loginResponse);

            if (loginResponse.error) {
              reject(new Error(loginResponse.error));
            } else {
              this.connected = true;
              this.emit("connected");
              resolve(this);
            }
          }
        );
      });
    });
  }

  send(words) {
    return new Promise((resolve, reject) => {
      if (!this.connected) {
        reject(new Error("Not connected"));
        return;
      }

      // Clear previous response data
      this.sentences = [];

      this._sendCommand(words, (response) => {
        if (response.error) {
          reject(new Error(response.error));
        } else {
          resolve(response.data);
        }
      });
    });
  }

  close() {
    return new Promise((resolve) => {
      if (this.socket) {
        this.socket.end();
        this.socket.on("close", () => {
          this.socket = null;
          this.connected = false;
          resolve();
        });
      } else {
        resolve();
      }
    });
  }

  _parseResponse() {
    while (this.buffer.length > 0) {
      const length = this._readLength();
      if (length === null) break;

      if (length === 0) {
        // End of sentence
        if (!this.currentSentence) {
          this.currentSentence = [];
        }

        // Only add non-empty sentences
        if (this.currentSentence.length > 0) {
          this.sentences.push(this.currentSentence);
        }

        // Reset for next sentence
        this.currentSentence = [];

        // Check if we have !done, which indicates end of response
        const lastSentence = this.sentences[this.sentences.length - 1];
        if (lastSentence && lastSentence[0] === "!done") {
          this._processCompleteResponse();
        }
      } else {
        // Read word data
        if (this.buffer.length < length) break;

        const word = iconv.decode(this.buffer.slice(0, length), "utf-8");
        this.buffer = this.buffer.slice(length);

        // Initialize current sentence if needed
        if (!this.currentSentence) {
          this.currentSentence = [];
        }

        this.currentSentence.push(word);
      }
    }
  }

  _processCompleteResponse() {
    if (!this.currentRequest) return;

    const callback = this.currentRequest;
    this.currentRequest = null;

    // Check for trap/error
    let error = null;
    for (const sentence of this.sentences) {
      if (sentence[0] === "!trap") {
        const msgItem = sentence.find((word) => word.startsWith("=message="));
        if (msgItem) {
          error = msgItem.substring(9);
        } else {
          error = "Unknown error";
        }
        break;
      }
    }

    // Convert !re sentences to data objects
    const data = [];
    for (const sentence of this.sentences) {
      if (sentence[0] === "!re") {
        const item = {};
        for (const word of sentence.slice(1)) {
          if (word.startsWith("=")) {
            const equalPos = word.indexOf("=", 1);
            if (equalPos !== -1) {
              const key = word.substring(1, equalPos);
              const value = word.substring(equalPos + 1);
              item[key] = value;
            }
          }
        }
        if (Object.keys(item).length > 0) {
          data.push(item);
        }
      }
    }

    // Clear sentences for next command
    const rawSentences = [...this.sentences];
    this.sentences = [];

    // Return the result
    callback({
      error: error,
      data: data,
      raw: rawSentences,
    });
  }

  _readLength() {
    if (this.buffer.length < 1) return null;

    const b = this.buffer[0];
    let len, size;

    if ((b & 0x80) === 0x00) {
      len = b;
      size = 1;
    } else if ((b & 0xC0) === 0x80) {
      if (this.buffer.length < 2) return null;
      len = ((b & ~0xC0) << 8) + this.buffer[1];
      size = 2;
    } else if ((b & 0xE0) === 0xC0) {
      if (this.buffer.length < 3) return null;
      len = ((b & ~0xE0) << 16) + (this.buffer[1] << 8) + this.buffer[2];
      size = 3;
    } else if ((b & 0xF0) === 0xE0) {
      if (this.buffer.length < 4) return null;
      len = ((b & ~0xF0) << 24) + (this.buffer[1] << 16) + (this.buffer[2] << 8) + this.buffer[3];
      size = 4;
    } else if (b === 0xF0) {
      if (this.buffer.length < 5) return null;
      len = (this.buffer[1] << 24) + (this.buffer[2] << 16) + (this.buffer[3] << 8) + this.buffer[4];
      size = 5;
    } else {
      throw new Error("Invalid length byte");
    }

    if (this.buffer.length < size) return null;

    this.buffer = this.buffer.slice(size);
    return len;
  }

  _writeLength(length) {
    const bytes = [];

    while (true) {
      let byte = length & 0x7f;
      length = length >> 7;

      if (length === 0) {
        bytes.push(byte);
        break;
      } else {
        bytes.push(byte | 0x80);
      }
    }

    return Buffer.from(bytes);
  }

  _encodeWord(word) {
    const wordBuf = Buffer.from(word, "utf8");
    const lengthBuf = this._writeLength(wordBuf.length);
    return Buffer.concat([lengthBuf, wordBuf]);
  }

  _sendCommand(words, callback) {
    this.currentRequest = callback;

    const data = Buffer.concat([
      ...words.map((word) => this._encodeWord(word)),
      this._writeLength(0), // End of sentence
    ]);

    this.socket.write(data);
  }
}

module.exports = RouterOSClient;
