/**
 * Copyright IBM Corp. 2016, 2025
 * SPDX-License-Identifier: BUSL-1.1
 */

import AdapterError from '@ember-data/adapter/error';
import { service } from '@ember/service';
import { hash, resolve } from 'rsvp';
import { assert } from '@ember/debug';
import { pluralize } from 'ember-inflector';

import ApplicationAdapter from './application';

const ENDPOINTS = [
  'health',
  'seal-status',
  'tokens',
  'token',
  'seal',
  'unseal',
  'init',
  'capabilities-self',
  'license',
];

const REPLICATION_ENDPOINTS = {
  reindex: 'reindex',
  recover: 'recover',
  status: 'status',

  primary: ['enable', 'disable', 'demote', 'secondary-token', 'revoke-secondary'],

  secondary: ['enable', 'disable', 'promote', 'update-primary'],
};

const REPLICATION_MODES = ['dr', 'performance'];
export default ApplicationAdapter.extend({
  version: service(),
  namespaceService: service('namespace'),
  shouldBackgroundReloadRecord() {
    return true;
  },

  findRecord(store, type, id, snapshot) {
    const fetches = {
      health: this.health(),
      sealStatus: this.sealStatus().catch((e) => e),
    };
    if (this.version.isEnterprise && this.namespaceService.inRootNamespace) {
      fetches.replicationStatus = this.replicationStatus().catch((e) => e);
    }
    return hash(fetches).then(({ health, sealStatus, replicationStatus }) => {
      let ret = {
        id,
        name: snapshot.attr('name'),
      };
      ret = Object.assign(ret, health);
      if (sealStatus instanceof AdapterError === false) {
        ret = Object.assign(ret, { nodes: [sealStatus] });
      }
      if (replicationStatus && replicationStatus instanceof AdapterError === false) {
        ret = Object.assign(ret, replicationStatus.data);
      } else if (
        replicationStatus instanceof AdapterError &&
        replicationStatus?.errors.find((err) => err === 'disabled path')
      ) {
        // set redacted if result is an error which only happens when redacted
        ret = Object.assign(ret, { replication_redacted: true });
      }
      return resolve(ret);
    });
  },

  pathForType(type) {
    return type === 'cluster' ? 'clusters' : pluralize(type);
  },

  health() {
    return this.ajax(this.urlFor('health'), 'GET', {
      data: {
        standbycode: 200,
        sealedcode: 200,
        uninitcode: 200,
        drsecondarycode: 200,
        performancestandbycode: 200,
      },
      unauthenticated: true,
    }).catch(() => {
      // sys/health will only fail when chroot set
      // because it's allowed in root namespace only and
      // configured to return a 200 response in other fail scenarios
      return { has_chroot_namespace: true };
    });
  },

  features() {
    return this.ajax(`${this.urlFor('license')}/features`, 'GET', {
      unauthenticated: true,
    });
  },

  sealStatus(unauthenticated = true) {
    return this.ajax(this.urlFor('seal-status'), 'GET', { unauthenticated });
  },

  seal() {
    return this.ajax(this.urlFor('seal'), 'PUT');
  },

  unseal(data) {
    return this.ajax(this.urlFor('unseal'), 'PUT', {
      data,
      unauthenticated: true,
    });
  },

  initCluster(data) {
    return this.ajax(this.urlFor('init'), 'PUT', {
      data,
      unauthenticated: true,
    });
  },

  mfaValidate({ mfa_request_id, mfa_constraints }) {
    const options = {
      data: {
        mfa_request_id,
        mfa_payload: mfa_constraints.reduce((obj, { selectedMethod, passcode }) => {
          let payload = [];
          if (passcode) {
            // duo requires passcode= prepended to the actual passcode
            // this isn't a great UX so we add it behind the scenes to fulfill the requirement
            // check if user added passcode= to avoid duplication
            payload =
              selectedMethod.type === 'duo' && !passcode.includes('passcode=')
                ? [`passcode=${passcode}`]
                : [passcode];
          }
          obj[selectedMethod.id] = payload;
          return obj;
        }, {}),
      },
    };
    return this.ajax('/v1/sys/mfa/validate', 'POST', options);
  },

  urlFor(endpoint) {
    if (!ENDPOINTS.includes(endpoint)) {
      throw new Error(
        `Calls to a ${endpoint} endpoint are not currently allowed in the vault cluster adapter`
      );
    }
    return `${this.buildURL()}/${endpoint}`;
  },

  urlForReplication(replicationMode, clusterMode, endpoint) {
    let suffix;
    const errString = `Calls to replication ${endpoint} endpoint are not currently allowed in the vault cluster adapater`;
    if (clusterMode) {
      assert(errString, REPLICATION_ENDPOINTS[clusterMode].includes(endpoint));
      suffix = `${replicationMode}/${clusterMode}/${endpoint}`;
    } else {
      assert(errString, REPLICATION_ENDPOINTS[endpoint]);
      suffix = `${endpoint}`;
    }
    return `${this.buildURL()}/replication/${suffix}`;
  },

  replicationStatus() {
    return this.ajax(`${this.buildURL()}/replication/status`, 'GET', { unauthenticated: true });
  },

  replicationDrPromote(data, options) {
    const verb = options && options.checkStatus ? 'GET' : 'PUT';
    return this.ajax(`${this.buildURL()}/replication/dr/secondary/promote`, verb, {
      data,
      unauthenticated: true,
    });
  },

  generateDrOperationToken(data, options) {
    let verb = 'POST';
    let url = `${this.buildURL()}/replication/dr/secondary/generate-operation-token/`;
    if (options?.cancel) {
      verb = 'DELETE';
      url += 'attempt';
    } else if (options?.checkStatus) {
      verb = 'GET';
      url += 'attempt';
    } else if (data?.pgp_key || data?.attempt) {
      url += 'attempt';
    } else {
      // progress the operation
      url += 'update';
    }
    return this.ajax(url, verb, {
      data,
      unauthenticated: true,
    });
  },

  replicationAction(action, replicationMode, clusterMode, data) {
    assert(
      `${replicationMode} is an unsupported replication mode.`,
      replicationMode && REPLICATION_MODES.includes(replicationMode)
    );

    const url =
      action === 'recover' || action === 'reindex'
        ? this.urlForReplication(replicationMode, null, action)
        : this.urlForReplication(replicationMode, clusterMode, action);

    return this.ajax(url, 'POST', { data });
  },
});
