import jwtDecode from 'jwt-decode';
import jose from 'node-jose';
import {isIOS} from '../constants';
import pem2jwk from 'simple-pem2jwk';
import {displayType, issuerType} from '../../machines/Issuers/IssuersMachine';
import getAllConfigurations, {CACHED_API} from '../api';

import i18next from 'i18next';
import {getJWT} from '../cryptoutil/cryptoUtil';
import i18n from '../../i18n';
import {
  Credential,
  CredentialWrapper,
  VerifiableCredentialType,
} from '../../machines/VerifiableCredential/VCMetaMachine/vc';
import {
  BOTTOM_SECTION_FIELDS_WITH_DETAILED_ADDRESS_FIELDS,
  DETAIL_VIEW_ADD_ON_FIELDS,
  getSelectedCredentialTypeDetails,
} from '../../components/VC/common/VCUtils';

export const Protocols = {
  OpenId4VCI: 'OpenId4VCI',
  OTP: 'OTP',
};

export const Issuers = {
  Mosip: '',
  Sunbird: 'Sunbird',
  ESignet: 'ESignet',
};

export const getIDTypeTranslations = (idType: VerifiableCredentialType) => {
  switch (idType) {
    case 'MOSIPVerifiableCredential':
      return i18n.t('VcDetails:nationalCard');
    case 'InsuranceCredential':
      return i18n.t('VcDetails:insuranceCard');
    case 'OpenG2PBeneficiaryVerifiableCredential':
      return i18n.t('VcDetails:beneficiaryCard');
    case 'OpenG2PRegistryVerifiableCredential':
      return i18n.t('VcDetails:socialRegistryCard');
    default: {
      return i18n.t('VcDetails:insuranceCard');
    }
  }
};

export const getIDType = (verifiableCredential: Credential) => {
  return verifiableCredential.type[1];
};

export const ACTIVATION_NEEDED = [Issuers.ESignet, Issuers.Mosip];

export const isActivationNeeded = (issuer: string) => {
  return ACTIVATION_NEEDED.indexOf(issuer) !== -1;
};

export const isActivationNotNeeded = (issuer: string) => {
  return ACTIVATION_NEEDED.indexOf(issuer) === -1;
};

export const Issuers_Key_Ref = 'OpenId4VCI_KeyPair';

export const getIdentifier = (context, credential) => {
  const credentialIdentifier = credential.credential.id;
  const credId = credentialIdentifier.startsWith('did')
    ? credentialIdentifier.split(':')
    : credentialIdentifier.split('/');
  return (
    context.selectedIssuer.credential_issuer +
    ':' +
    context.selectedIssuer.protocol +
    ':' +
    credId[credId.length - 1]
  );
};

export const getBody = async context => {
  const header = {
    alg: 'RS256',
    jwk: await getJWK(context.publicKey),
    typ: 'openid4vci-proof+jwt',
  };
  const decodedToken = jwtDecode(context.tokenResponse.accessToken);
  const payload = {
    iss: context.selectedIssuer.client_id,
    nonce: decodedToken.c_nonce,
    aud: context.selectedIssuer.credential_audience,
    iat: Math.floor(new Date().getTime() / 1000),
    exp: Math.floor(new Date().getTime() / 1000) + 18000,
  };

  const proofJWT = await getJWT(
    header,
    payload,
    Issuers_Key_Ref,
    context.privateKey,
  );
  return {
    format: 'ldp_vc',
    credential_definition: {
      '@context': ['https://www.w3.org/2018/credentials/v1'],
      type: getCredentialType(context),
    },
    proof: {
      proof_type: 'jwt',
      jwt: proofJWT,
    },
  };
};

export const getCredentialType = (context: any) => {
  return context.selectedCredentialType?.credential_definition?.type
    ? context.selectedCredentialType.credential_definition.type
    : context.selectedIssuer?.credential_type
    ? context.selectedIssuer.credential_type
    : ['VerifiableCredential', 'MOSIPVerifiableCredential'];
};

export const updateCredentialInformation = (context, credential) => {
  let credentialWrapper: CredentialWrapper = {};
  credentialWrapper.verifiableCredential = credential;
  credentialWrapper.identifier = getIdentifier(context, credential);
  credentialWrapper.generatedOn = new Date();
  credentialWrapper.verifiableCredential.wellKnown =
    context.selectedIssuer['.well-known'];
  credentialWrapper.verifiableCredential.credentialTypes =
    getCredentialType(context);
  credentialWrapper.verifiableCredential.issuerLogo =
    getDisplayObjectForCurrentLanguage(context.selectedIssuer.display)?.logo;
  credentialWrapper.vcMetadata = context.vcMetadata || {};
  return credentialWrapper;
};

export const updateVCmetadataOfCredentialWrapper = (
  context,
  credentialWrapper: CredentialWrapper,
) => {
  credentialWrapper.vcMetadata = context.vcMetadata;
  return credentialWrapper;
};

export const getDisplayObjectForCurrentLanguage = (
  display: [displayType],
): displayType => {
  const currentLanguage = i18next.language;
  const languageKey = Object.keys(display[0]).includes('language')
    ? 'language'
    : 'locale';
  let displayType = display.filter(
    obj => obj[languageKey] == currentLanguage,
  )[0];
  if (!displayType) {
    displayType = display.filter(obj => obj[languageKey] === 'en')[0];
  }
  return displayType;
};

export const constructAuthorizationConfiguration = (
  selectedIssuer: issuerType,
  supportedScopes: string,
) => {
  return {
    clientId: selectedIssuer.client_id,
    scopes: supportedScopes,
    additionalHeaders: selectedIssuer.additional_headers,
    redirectUrl: selectedIssuer.redirect_uri,
    serviceConfiguration: {
      authorizationEndpoint: selectedIssuer.authorization_endpoint,
      tokenEndpoint: selectedIssuer.token_endpoint,
    },
  };
};

export const getJWK = async publicKey => {
  try {
    let publicKeyJWKString;
    let publicKeyJWK;
    if (isIOS()) {
      publicKeyJWKString = await jose.JWK.asKey(publicKey, 'pem');
      publicKeyJWK = publicKeyJWKString.toJSON();
    } else {
      publicKeyJWK = await pem2jwk(publicKey);
    }
    return {
      ...publicKeyJWK,
      alg: 'RS256',
      use: 'sig',
    };
  } catch (e) {
    console.error(
      'Exception occured while constructing JWK from PEM : ' +
        publicKey +
        '  Exception is ',
      e,
    );
  }
};

export const getCredentialIssuersWellKnownConfig = async (
  issuer: string,
  wellknown: string,
  vcCredentialTypes: Object[],
  defaultFields: string[],
) => {
  let fields: string[] = defaultFields;
  let response = null;
  let credentialDetails;
  if (wellknown) {
    response = await CACHED_API.fetchIssuerWellknownConfig(issuer, wellknown);
    if (response) {
      credentialDetails = getSelectedCredentialTypeDetails(
        response,
        vcCredentialTypes,
      );
      if (Object.keys(credentialDetails).includes('order')) {
        fields = credentialDetails.order;
      } else {
        fields = Object.keys(
          credentialDetails.credential_definition.credentialSubject,
        );
      }
    } else {
      fields = [];
    }
  }
  return {
    wellknown: credentialDetails,
    fields: fields,
  };
};

export const getDetailedViewFields = async (
  issuer: string,
  wellknown: string,
  vcCredentialTypes: Object[],
  defaultFields: string[],
) => {
  let response = await getCredentialIssuersWellKnownConfig(
    issuer,
    wellknown,
    vcCredentialTypes,
    defaultFields,
  );

  let updatedFieldsList = response.fields.concat(DETAIL_VIEW_ADD_ON_FIELDS);

  updatedFieldsList = removeBottomSectionFields(updatedFieldsList);

  return {
    wellknown: response.wellknown,
    fields: updatedFieldsList,
  };
};

export const removeBottomSectionFields = fields => {
  return fields.filter(
    fieldName =>
      !BOTTOM_SECTION_FIELDS_WITH_DETAILED_ADDRESS_FIELDS.includes(fieldName) &&
      fieldName !== 'address',
  );
};

export const vcDownloadTimeout = async (): Promise<number> => {
  const response = await getAllConfigurations();

  return Number(response['openId4VCIDownloadVCTimeout']) || 30000;
};

// OIDCErrors is a collection of external errors from the OpenID library or the issuer
export enum OIDCErrors {
  OIDC_FLOW_CANCELLED_ANDROID = 'User cancelled flow',
  OIDC_FLOW_CANCELLED_IOS = 'org.openid.appauth.general error -3',

  INVALID_TOKEN_SPECIFIED = 'Invalid token specified',
  OIDC_CONFIG_ERROR_PREFIX = 'Config error',
}

// ErrorMessage is the type of error message shown in the UI
export enum ErrorMessage {
  NO_INTERNET = 'noInternetConnection',
  GENERIC = 'generic',
  REQUEST_TIMEDOUT = 'requestTimedOut',
  BIOMETRIC_CANCELLED = 'biometricCancelled',
}
