/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import Identifier from '../Identifier';
import UserAgentError from '../UserAgentError';
import ClaimObject from '../credentials/ClaimObject';
import OidcResponse from './OidcResponse';
import OIDCAuthenticationRequest from '../crypto/protocols/did/requests/OIDCAuthenticationRequest';
import CryptoOptions from '../CryptoOptions';
import CryptoFactory from '../crypto/plugin/CryptoFactory';
import JwsToken from '../crypto/protocols/jose/jws/JwsToken';
import UserAgentOptions from '../UserAgentOptions';
import HttpResolver from '../resolvers/HttpResolver';
import VerifyHelper from './VerifyHelper';

/**
 * Optional Parameters to add to OIDCRequests.
 */
export type OptionalOIDCRequestParams = {
  /**
   * Opaque value to represent state on serverside.
   */
  state?: string;

  /**
   * ClaimObject if OIDC Request involves issuance of credential.
   */
  claimObject?: ClaimObject;

  /**
   * Claim Requests if requesting to be presented with credential.
   */
  claimsRequested?: string[];
};

/**
 * Standard response type for SIOP.
 */
const responseType = 'id_token'

/**
 * Standard response mode for SIOP.
 */
const responseMode = 'form_post'

/**
 * Standard scope for SIOP.
 */
const scope = 'openid did_authn'

/**
 * Class to represent Open ID Connect Self-Issued Tokens
 * @class
 */
export default class OidcRequest {

  /**
   * Redirect URL for SIOP.
   */
  public redirectUrl: string;

  /**
   * Nonce for SIOP.
   */
  public nonce: string;

  /**
   * Sender of the request.
   */
  public sender: Identifier;

  /**
   * Optional claimObject attached to OIDC request.
   */
  public claimObject?: ClaimObject;

  /**
   * Opaque value to represent state on serverside.
   */
  public state?: string;

  /**
   * Optional Claim that is requested for presentation.
   */
  public claimsRequested?: string[];

  /**
   * Instantiates an self-signed OIDC Request.
   * @param sender Identifier who will sign request.
   * @param redirectUrl Redirect URL for SIOP.
   * @param nonce Nonce for SIOP.
   * @param claimObject optional claimObject to attach to request.
   */
  constructor(sender: Identifier, redirectUrl: string, nonce: string, options?: OptionalOIDCRequestParams) {
    this.sender = sender;
    this.redirectUrl = redirectUrl;
    this.nonce = nonce;
    this.claimObject = options!.claimObject;
    this.state = options!.state;
    this.claimsRequested = options!.claimsRequested;
  }

  /**
   * Forms the request to spec and sign the request.
   * @param keyReference 
   * @returns jwt in compact form.
   */
  public async sign(keyReference: string): Promise<string> {
    
    const request: Partial<OIDCAuthenticationRequest> = {
      iss: this.sender.id,
      response_type: responseType,
      response_mode: responseMode,
      client_id: this.redirectUrl,
      scope,
      nonce: this.nonce
    }

    if (this.state) {
      Object.assign(request, {state: this.state});
    }

    if (this.claimsRequested) {
      const claimsRequestIdToken: {[key: string]: any} = {};
      // Assuming every claim requested is essential for now.
      this.claimsRequested.forEach(claimRef => {
        claimsRequestIdToken[claimRef] = {
          essential: true
        }
      });
      Object.assign(request, {claims: {id_token: claimsRequestIdToken}});
    }

    if (this.claimObject) {
      Object.assign(request, {offer: JSON.parse(this.claimObject.serialize())});
    }
    
    return this.sender.sign(request, keyReference);
  }

  /**
   * Parses and Verifies signed JWT.
   * @param signedRequest signed JWT containing OIDC request.
   * @returns OidcRequest Object if verified.
   */
  public static async verifyAndParse(signedRequest: string, cryptoFactory?: CryptoFactory): Promise<OidcRequest> {

    if (!cryptoFactory) {
      cryptoFactory = new CryptoOptions().cryptoFactory;
    }

    // get identifier id from key id in header.
    const token : JwsToken = await JwsToken.deserialize(signedRequest, {cryptoFactory});
    const request = JSON.parse(token.payload.toString());
    const senderId = request.iss;

    const options = new UserAgentOptions();
    options.resolver = new HttpResolver('https://beta.discover.did.microsoft.com');
    const sender = new Identifier(senderId, options)

    // if (!await VerifyHelper.verify(sender, token)) {
    console.log('not verifying for now');
      // throw new UserAgentError(`Invalid signature for token issued by: ${request.iss}`);
    // }

    const requestOptions: OptionalOIDCRequestParams = {
      state: request.state,
      claimObject: request.offer,
    };

    // parse out requested claims to list if param is present.
    if (request.claims && request.claims.id_token) {
      const claimsRequested = Object.keys(request.claims.id_token);
      Object.assign(requestOptions, {claimsRequested});
    }
    return new OidcRequest(sender, request.client_id, request.nonce, requestOptions);
  }

    /**
     * Respond to OIDC Request using identifier on the client side.
     * @param identifier the identifier used to sign response
     * @returns the body of the HTTP response if receive status 200.
     */
    public async respondWith(identifier: Identifier, keyReference: string, claimObjects?: ClaimObject[]): Promise<string> {
      const oidcResponse = await OidcResponse.create(this, identifier);
      return oidcResponse.signAndSend(keyReference, claimObjects);
    }
}
