'use strict';

var _ = require('lodash');
var jwt = require('jsonwebtoken');
var util = require('util');


/**
 * @constructor
 * @param {object} options - ...
 * @param {string} options.workspaceSid - The workspace unique ID
 * @param {string} options.workerSid - The worker unique ID
 * @param {string} options.role - The role of the grant
 */
function TaskRouterGrant(options) {
  options = options || {};
  this.workspaceSid = options.workspaceSid;
  this.workerSid = options.workerSid;
  this.role = options.role;
}

_.extend(TaskRouterGrant.prototype, {
  key: 'task_router',

  toPayload: function() {
    var grant = {};
    if (this.workspaceSid) { grant.workspace_sid = this.workspaceSid; }
    if (this.workerSid) { grant.worker_sid = this.workerSid; }
    if (this.role) { grant.role = this.role; }
    return grant;
  }
});


/**
 * @constructor
 * @param {object} options - ...
 * @param {string} options.serviceSid - The service unique ID
 * @param {string} options.endpointId - The endpoint ID
 * @param {string} options.deploymentRoleSid - SID of the deployment role to be
 *                 assigned to the user
 * @param {string} options.pushCredentialSid - The Push Credentials SID
 */
function ChatGrant(options) {
  options = options || {};
  this.serviceSid = options.serviceSid;
  this.endpointId = options.endpointId;
  this.deploymentRoleSid = options.deploymentRoleSid;
  this.pushCredentialSid = options.pushCredentialSid;
}

_.extend(ChatGrant.prototype, {
  key: 'chat',

  toPayload: function() {
    var grant = {};
    if (this.serviceSid) { grant.service_sid = this.serviceSid; }
    if (this.endpointId) { grant.endpoint_id = this.endpointId; }
    if (this.deploymentRoleSid) {
      grant.deployment_role_sid = this.deploymentRoleSid;
    }
    if (this.pushCredentialSid) {
      grant.push_credential_sid = this.pushCredentialSid;
    }
    return grant;
  }
});

/**
 * @deprecated - Use ChatGrant instead.
 *
 * @constructor
 * @param {object} options - ...
 * @param {string} options.serviceSid - The service unique ID
 * @param {string} options.endpointId - The endpoint ID
 * @param {string} options.deploymentRoleSid - SID of the deployment role to be
 *                 assigned to the user
 * @param {string} options.pushCredentialSid - The Push Credentials SID
 */
function IpMessagingGrant(options) {
  ChatGrant.call(this, options);
}

IpMessagingGrant.prototype = _.create(ChatGrant.prototype, _.assign({
  '_super': ChatGrant.prototype,
  'constructor': ChatGrant
}));

IpMessagingGrant.prototype.key = 'ip_messaging';

/**
  * @deprecated - Use VideoGrant instead
  *
  * @constructor
  * @param {object} options - ...
  * @param {string} options.configurationProfileSid - The configuration
  *                 profile unique ID
  */
function ConversationsGrant(options) {
  options = options || {};
  this.configurationProfileSid = options.configurationProfileSid;
}

_.extend(ConversationsGrant.prototype, {
  key: 'rtc',
  toPayload: function() {
    var grant = {};
    if (this.configurationProfileSid) {
      grant.configuration_profile_sid = this.configurationProfileSid;
    }
    return grant;
  }
});

/**
 * @constructor
 * @param {object} options - ...
 * @param {string} options.room - The Room name or Room sid.
 */
function VideoGrant(options) {
  options = options || {};
  this.room = options.room;
}

_.extend(VideoGrant.prototype, {
  key: 'video',
  toPayload: function() {
    var grant = {};
    if (this.room) { grant.room = this.room; }
    return grant;
  }
});

/**
 * @constructor
 * @param {string} options.serviceSid - The service unique ID
 * @param {string} options.endpointId - The endpoint ID
 */
function SyncGrant(options) {
  options = options || {};
  this.serviceSid = options.serviceSid;
  this.endpointId = options.endpointId;
}

_.extend(SyncGrant.prototype, {
  key: 'data_sync',

  toPayload: function() {
    var grant = {};
    if (this.serviceSid) { grant.service_sid = this.serviceSid; }
    if (this.endpointId) { grant.endpoint_id = this.endpointId; }
    return grant;
  }
});

/**
 * @constructor
 * @param {object} options - ...
 * @param {boolean} options.incomingAllow - Whether or not this endpoint is allowed to receive incoming calls as grants.identity
 * @param {string} options.outgoingApplicationSid - application sid to call when placing outgoing call
 * @param {object} options.outgoingApplicationParams - request params to pass to the application
 * @param {string} options.pushCredentialSid - Push Credential Sid to use when registering to receive incoming call notifications
 * @param {string} options.endpointId - Specify an endpoint identifier for this device, which will allow the developer
 *                 to direct calls to a specific endpoint when multiple devices are associated with a single identity
 */
function VoiceGrant(options) {
  options = options || {};
  this.incomingAllow = options.incomingAllow;
  this.outgoingApplicationSid = options.outgoingApplicationSid;
  this.outgoingApplicationParams = options.outgoingApplicationParams;
  this.pushCredentialSid = options.pushCredentialSid;
  this.endpointId = options.endpointId;
}

_.extend(VoiceGrant.prototype, {
  key: 'voice',
  toPayload: function() {
    var grant = {};
    if (this.incomingAllow === true) {
      grant.incoming = { allow: true };
    }

    if (this.outgoingApplicationSid) {
      grant.outgoing = {};
      grant.outgoing.application_sid = this.outgoingApplicationSid;

      if (this.outgoingApplicationParams) {
        grant.outgoing.params = this.outgoingApplicationParams;
      }
    }

    if (this.pushCredentialSid) {
      grant.push_credential_sid = this.pushCredentialSid;
    }
    if (this.endpointId) {
      grant.endpoint_id = this.endpointId;
    }
    return grant;
  }
});

/**
 * @constructor
 * @param {object} options - ...
 * @param {string} options.grant - The PlaybackGrant retrieved from Twilio's API
 */
function PlaybackGrant(options) {
  options = options || {};
  this.grant = options.grant;
}

_.extend(PlaybackGrant.prototype, {
  key: 'player',
  toPayload: function() {
    var grant = {};
    if (this.grant) { grant = this.grant; }
    return grant;
  }
});

/**
 * @constructor
 * @param {string} accountSid - The account's unique ID to which access is scoped
 * @param {string} keySid - The signing key's unique ID
 * @param {string} secret - The secret to sign the token with
 * @param {object} options - ...
 * @param {number} [options.ttl=3600] - Time to live in seconds
 * @param {string} [options.identity] - The identity of the first person
 * @param {number} [options.nbf] - Time from epoch in seconds for not before value
 * @param {string} [options.region] - The region value associated with this account
 */
function AccessToken(accountSid, keySid, secret, options) {
  if (!accountSid) { throw new Error('accountSid is required'); }
  if (!keySid) { throw new Error('keySid is required'); }
  if (!secret) { throw new Error('secret is required'); }
  options = options || {};

  this.accountSid = accountSid;
  this.keySid = keySid;
  this.secret = secret;
  this.ttl = options.ttl || 3600;
  this.identity = options.identity;
  this.nbf = options.nbf;
  this.region = options.region;
  this.grants = [];
}

// Class level properties
AccessToken.IpMessagingGrant = util.deprecate(IpMessagingGrant, 'IpMessagingGrant is deprecated, use ChatGrant instead.');
AccessToken.ChatGrant = ChatGrant;
AccessToken.VoiceGrant = VoiceGrant;
AccessToken.SyncGrant = SyncGrant;
AccessToken.VideoGrant = VideoGrant;
AccessToken.ConversationsGrant = util.deprecate(ConversationsGrant, 'ConversationsGrant is deprecated, use VideoGrant instead.');
AccessToken.TaskRouterGrant = TaskRouterGrant;
AccessToken.PlaybackGrant = PlaybackGrant;
AccessToken.DEFAULT_ALGORITHM = 'HS256';
AccessToken.ALGORITHMS = [
  'HS256',
  'HS384',
  'HS512'
];

_.extend(AccessToken.prototype, {
  addGrant: function(grant) {
    this.grants.push(grant);
  },

  toJwt: function(algorithm) {
    algorithm = algorithm || AccessToken.DEFAULT_ALGORITHM;
    if (!_.includes(AccessToken.ALGORITHMS, algorithm)) {
      throw new Error('Algorithm not supported. Allowed values are ' + AccessToken.ALGORITHMS.join(', '));
    }

    var grants = {};
    if (_.isInteger(this.identity) || _.isString(this.identity)) { grants.identity = String(this.identity); }

    _.each(this.grants, function(grant) {
      grants[grant.key] = grant.toPayload();
    });

    var now = Math.floor(Date.now() / 1000);
    var payload = {
      jti: this.keySid + '-' + now,
      grants: grants
    };
    if (_.isNumber(this.nbf)) { payload.nbf = this.nbf; }

    var header = {
      cty: 'twilio-fpa;v=1',
      typ: 'JWT'
    };

    if (this.region && _.isString(this.region)) {
      header.twr = this.region;
    }

    return jwt.sign(payload, this.secret, {
      header: header,
      algorithm: algorithm,
      issuer: this.keySid,
      subject: this.accountSid,
      expiresIn: this.ttl
    });
  }
});


module.exports = AccessToken;
