import request from 'superagent';
import { ProxyAgent } from 'proxy-agent';

export default class VaultService {

  constructor(config) {
      this.request = request;
      // Prefer HTTPS proxy if set, otherwise HTTP; support upper/lower-case env vars
      const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
      const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy;
      if (httpsProxy || httpProxy){
        this.proxy = httpsProxy || httpProxy;
        this.proxyAgent = new ProxyAgent(this.proxy);
      }
      this.token = false;
    if (config.vaultConfig.token) {
        this.token = config.vaultConfig.token;
      }
      this.config = config.vaultConfig;
      // KV engine version; default to v1 for backward compatibility
      this.version = (this.config.version || 'v1').toString().toLowerCase();
  }

  async login() {
    let data = { "password": this.config.password };

      try {
        let response;
        if (this.proxy) {
          if (this.config.namespace){
            response = await this.request.post(this.config.endpoint + "/auth/userpass/login/" + this.config.user).set('X-Vault-Namespace', this.config.namespace).agent(this.proxyAgent).send(data);
          } else {
            response = await this.request.post(this.config.endpoint + "/auth/userpass/login/" + this.config.user).agent(this.proxyAgent).send(data);
          }
        } else {
          if (this.config.namespace){
            response = await this.request.post(this.config.endpoint + "/auth/userpass/login/" + this.config.user).set('X-Vault-Namespace', this.config.namespace).send(data);
          } else {
            response = await this.request.post(this.config.endpoint + "/auth/userpass/login/" + this.config.user).send(data);
          }
        }
        if (response && response.body && response.body.auth && response.body.auth.client_token) {
          this.token = response.body.auth.client_token;
        } else {
            throw new Error('Vault Service Login Response did not include token');
        }
      } catch (error) {
          throw new Error('Error vault service login: ' + error)
      }

  }

  async listKeys(path) {
      // Normalize base path (mount and optional prefix), ensure leading and trailing slash
      if (!path.startsWith('/') && !this.config.endpoint.endsWith('/')){
          path = '/' + path;
      }
      if (!path.endsWith('/')){
        path = path + '/' ;
      }

      // KV v2 listing happens under /metadata
      if (this.version === 'v2' && path.indexOf('/metadata/') === -1) {
        const firstSegmentEnd = path.indexOf('/', 1);
        if (firstSegmentEnd > -1) {
          path = path.slice(0, firstSegmentEnd) + '/metadata' + path.slice(firstSegmentEnd);
        }
      }

      let listPath = path + '?list=true';
      try {
        if (!this.token) {
          await this.login();
        }
        let response;
        if (this.proxy) {
          if (this.config.namespace){
            response = await this.request.get(this.config.endpoint + listPath).agent(this.proxyAgent).set('X-Vault-Token', this.token).set('X-Vault-Namespace', this.config.namespace);
          } else {
            response = await this.request.get(this.config.endpoint + listPath).agent(this.proxyAgent).set('X-Vault-Token', this.token);
          }
        } else {
          if (this.config.namespace){
            response = await this.request.get(this.config.endpoint + listPath).set('X-Vault-Token', this.token).set('X-Vault-Namespace', this.config.namespace);
          } else {
            response = await this.request.get(this.config.endpoint + listPath).set('X-Vault-Token', this.token);
          }
        }
        if (response && response.body && response.body.data && Array.isArray(response.body.data.keys)){
          return response.body.data.keys;
        }
        throw new Error('listKeys failed for: ' + path);
      } catch (error) {
        throw new Error('Error vault service listKeys on path: ' + this.config.endpoint + listPath + ' ERROR: ' + error)
      }
  }

  async getSecret(path, key, options = {}) {

      if (!path.startsWith('/') && !this.config.endpoint.endsWith('/')){
          path = '/' + path;
      }
      if (!path.endsWith('/')){
        path = path + '/' ;
      }
      path = path + key;

      // KV v2: insert '/data' after mount path for reads
      if (this.version === 'v2' && path.indexOf('/data/') === -1) {
        const firstSegmentEnd = path.indexOf('/', 1);
        if (firstSegmentEnd > -1) {
          path = path.slice(0, firstSegmentEnd) + '/data' + path.slice(firstSegmentEnd);
        }
      }
      // KV v2: allow reading a specific version via query param
      if (this.version === 'v2' && options && options.version) {
        path = path + '?version=' + options.version;
      }

      try {
        if (!this.token) {
          await this.login();
        }
        let response;
        if (this.proxy) {
          if (this.config.namespace){
            response = await this.request.get(this.config.endpoint + path).agent(this.proxyAgent).set('X-Vault-Token', this.token).set('X-Vault-Namespace', this.config.namespace);
          } else {
            response = await this.request.get(this.config.endpoint + path).agent(this.proxyAgent).set('X-Vault-Token', this.token);
          }
        } else {
          if (this.config.namespace){
            response = await this.request.get(this.config.endpoint + path).set('X-Vault-Token', this.token).set('X-Vault-Namespace', this.config.namespace);
          } else {
            response = await this.request.get(this.config.endpoint + path).set('X-Vault-Token', this.token);
          }
        }
        // KV v1: body.data; KV v2: body.data.data
        const bodyData = this.version === 'v2' ? (response.body && response.body.data && response.body.data.data) : (response.body && response.body.data);
        if (bodyData){
          let result = {};
          result[key] = bodyData
          return result;
        } else {
          throw new Error('getSecret failed for:' + key);
        }
      } catch (error) {
        //retry
        try {
          if (!this.token) {
            await this.login();
          }
          let response;
          if (this.proxy) {
            if (this.config.namespace){
              response = await this.request.get(this.config.endpoint + path).agent(this.proxyAgent).set('X-Vault-Token', this.token).set('X-Vault-Namespace', this.config.namespace);
            } else {
              response = await this.request.get(this.config.endpoint + path).agent(this.proxyAgent).set('X-Vault-Token', this.token);
            }
          } else {
            if (this.config.namespace){
              response = await this.request.get(this.config.endpoint + path).set('X-Vault-Token', this.token).set('X-Vault-Namespace', this.config.namespace);
            } else {
              response = await this.request.get(this.config.endpoint + path).set('X-Vault-Token', this.token);
            }
          }
          const bodyData = this.version === 'v2' ? (response.body && response.body.data && response.body.data.data) : (response.body && response.body.data);
          if (bodyData){
            let result = {};
            result[key] = bodyData
            return result;
          } else {
            throw new Error('getSecret failed for:' + key);
          }
        } catch (error) {
            throw new Error('Error vault service getSecret on path: ' + this.config.endpoint + path + ' ERROR: ' + error)
        }
      }

  }
  /**
   * Create or update a secret at the given path/key.
   *
   * KV v2 versioning:
   * - Each successful write creates a new version.
   * - Use CAS (check-and-set) to control concurrency and avoid unintended overwrites.
   *   - options.cas === 0: Create-only. Succeeds only if no version exists yet.
   *   - options.cas === currentVersion: Update-only if the current stored version equals the provided number.
   *   - options.cas is undefined: Unconditional write (always creates a new version), not concurrency-safe.
   *
   * KV v1 ignores CAS and writes the value directly.
   *
   * @param {string} path - Base secret path (e.g. '/secret/app')
   * @param {string} key - Secret key name (final path segment)
   * @param {object} data - Secret payload to store
   * @param {object} [options] - Additional write options
   * @param {number} [options.cas] - KV v2 check-and-set value (0 for create-only, N for update-if-version-is-N)
   */
  async createUpdateSecret(path, key, data, options = {}) {
      if (!path.startsWith('/') && !this.config.endpoint.endsWith('/')){
          path = '/' + path;
      }
      if (!path.endsWith('/')){
        path = path + '/' ;
      }
      path = path + key;

      // KV v2: insert '/data' after mount path for writes
      if (this.version === 'v2' && path.indexOf('/data/') === -1) {
        const firstSegmentEnd = path.indexOf('/', 1);
        if (firstSegmentEnd > -1) {
          path = path.slice(0, firstSegmentEnd) + '/data' + path.slice(firstSegmentEnd);
        }
      }

      // Build request payload depending on version and check-and-set option
      let payload = data;
      if (this.version === 'v2') {
        payload = { data: data };
        if (options && (typeof options.cas !== 'undefined')) {
          payload.options = { cas: options.cas };
        }
      }

      try {
        if (!this.token) {
          await this.login();
        }
        let response;
        if (this.proxy) {
          if (this.config.namespace){
            response = await this.request.post(this.config.endpoint + path).send(payload).agent(this.proxyAgent).set('X-Vault-Token', this.token).set('X-Vault-Namespace', this.config.namespace);
          } else {
            response = await this.request.post(this.config.endpoint + path).send(payload).agent(this.proxyAgent).set('X-Vault-Token', this.token);
          }
        } else {
          if (this.config.namespace){
            response = await this.request.post(this.config.endpoint + path).send(payload).set('X-Vault-Token', this.token).set('X-Vault-Namespace', this.config.namespace);
          } else {
            response = await this.request.post(this.config.endpoint + path).send(payload).set('X-Vault-Token', this.token);
          }
        }
        // Accept 200/204 as success; map body if present (v2 returns metadata under body.data)
        if ((response && (response.status === 200 || response.status === 204)) || (response && response.body && response.body.data)){
          let result = {};
          result[key] = (response.body && response.body.data) ? response.body.data : {};
          return result;
        }
        throw new Error('createUpdateSecret failed for:' + key);
      } catch (error) {
        //retry
        try {
          if (!this.token) {
            await this.login();
          }
          let response;
          if (this.proxy) {
            if (this.config.namespace){
              response = await this.request.post(this.config.endpoint + path).send(payload).agent(this.proxyAgent).set('X-Vault-Token', this.token).set('X-Vault-Namespace', this.config.namespace);
            } else {
              response = await this.request.post(this.config.endpoint + path).send(payload).agent(this.proxyAgent).set('X-Vault-Token', this.token);
            }
          } else {
            if (this.config.namespace){
              response = await this.request.post(this.config.endpoint + path).send(payload).set('X-Vault-Token', this.token).set('X-Vault-Namespace', this.config.namespace);
            } else {
              response = await this.request.post(this.config.endpoint + path).send(payload).set('X-Vault-Token', this.token);
            }
          }
          if ((response && (response.status === 200 || response.status === 204)) || (response && response.body && response.body.data)){
            let result = {};
            result[key] = (response.body && response.body.data) ? response.body.data : {};
            return result;
          }
          throw new Error('createUpdateSecret failed for:' + key);
        } catch (error) {
          throw new Error('Error vault service createUpdateSecret: ' + error)
        }
      }

  }
}
