import axios from 'axios';

/**
 * Handle authentication and send/receive with the backbone
 */
export class Adapter {
  constructor(protocol, config) {
    this.protocol = protocol;
    this.config = config;
    this.axios = axios;
    this.authenticating = false;
  }

  /**
   * Test parsing the message based on the provided protocol schema
   *
   * The message must be successfully json decoded into an object
   * prior to validation
   *
   * At present this returns the validation result which is an
   * object containing a boolean valid field as well as details
   * of any errors.
   * @param {object} message
   * @returns {object}
   */
  validate(message) {
    return this.protocol.validate(message);
  }

  /**
   * Ensure the token in this.credentials is still valid
   * @returns {boolean}
   */
  tokenValid() {
    let valid = false;
    if (this.credentials) {
      const now = new Date().toISOString();
      valid = this.credentials.expiry > now;
    }
    return valid;
  }

  /**
   * Return an http headers object containing a bearer token authorisation header
   * @returns {object}
   */
  getAuthorizationHeader() {
    if (this.authenticating) {
      // return an pseudo response with an ignored status
      return Promise.reject({ response: { status: 405 } });
    }
    if (!this.tokenValid())
      return this.auth().then((response) => {
        return {
          Authorization: `Bearer ${response.data.token}`,
        };
      });
    else {
      return Promise.resolve({
        Authorization: `Bearer ${this.credentials.token}`,
      });
    }
  }

  /**
   * Do a client credentials grant and store in this.credentials
   * @returns {object}
   */
  auth() {
    this.authenticating = true;
    let adapterConfig = this.config;
    return this.axios
      .get(`${adapterConfig.api}/token`, {
        params: {
          client_id: adapterConfig.client_id,
          secret: adapterConfig.secret,
        },
      })
      .then((response) => {
        this.credentials = response.data;
        this.authenticating = false;
        return response;
      })
      .catch((error) => {
        this.authenticating = false;
        return Promise.reject(error);
      });
  }

  /**
   * Call the GET /receive endpoint and process the messages with decode
   *
   * Returns the response
   * @param {boolean} is_retry
   * @returns {object}
   */
  poll(is_retry = false) {
    let adapterConfig = this.config;
    return this.getAuthorizationHeader()
      .then((headers) => {
        return this.axios.get(`${adapterConfig.api}/receive`, {
          headers,
        });
      })
      .then((response) => {
        response.data.forEach((message) => {
          const parsed = JSON.parse(message.message);
          const validation = this.validate(parsed);
          if (validation.valid) {
            const type = this.protocol.getType(parsed);
            this.protocol.decode(type, parsed);
          } else {
            this.protocol.receivedInvalid(parsed, validation);
          }
        });
        return response;
      })
      .catch((error) => {
        let retry = false;
        switch (error.response.status) {
          case 403:
            {
              this.credentials = null;
              retry = true;
            }
            break;
          // ignore 405 from auth in progress
          case 503: {
            retry = true;
          }
        }
        if (retry && !is_retry) return this.poll(true);
        else return Promise.reject(error);
      });
  }

  /**
   * Publish a message to the backbone with the specified topic
   *
   * Messages should be passed through encode before sending
   * @param {string} topic
   * @param {string} body
   * @param {boolean} is_retry
   * @returns
   */
  publish(topic, body, is_retry = false) {
    let adapterConfig = this.config;
    return this.getAuthorizationHeader()
      .then((headers) => {
        return this.axios.post(
          `${adapterConfig.api}/send`,
          {
            topic,
            body,
          },
          {
            headers,
          }
        );
      })
      .then((response) => {
        return response;
      })
      .catch((error) => {
        let retry = false;
        switch (error.response.status) {
          case 403:
            {
              this.credentials = null;
              retry = true;
            }
            break;
          case 503: {
            retry = true;
          }
        }
        if (retry && !is_retry) return this.publish(topic, body, true);
        else return Promise.reject(error);
      });
  }

  /**
   * Broadcast the message on the backbone
   *
   * Broadcast messages bypass the normal publisher queues
   * this means they can be used to deliver messages more
   * quickly in an emergency scenario.
   *
   * Messages should be passed through encode before sending
   * @param {string} body
   * @param {boolean} is_retry
   * @returns
   */
  broadcast(body, is_retry = false) {
    let adapterConfig = this.config;
    return this.getAuthorizationHeader()
      .then((headers) => {
        return this.axios.post(
          `${adapterConfig.api}/notify`,
          {
            body,
          },
          {
            headers,
          }
        );
      })
      .then((response) => {
        return response;
      })
      .catch((error) => {
        let retry = false;
        switch (error.response.status) {
          case 403:
            {
              this.credentials = null;
              retry = true;
            }
            break;
          case 503: {
            retry = true;
          }
        }
        if (retry && !is_retry) return this.broadcast(body, true);
        else return Promise.reject(error);
      });
  }
}