import { getSDAlgAndPayload } from './decode';
import { FlattenJSON } from './flattenJSON';
import { GeneralJSON } from './generalJSON';
import { Jwt, type VerifierOptions } from './jwt';
import { KBJwt } from './kbjwt';
import { pack, SDJwt } from './sdjwt';
import {
  type DisclosureFrame,
  type Hasher,
  IANA_HASH_ALGORITHMS,
  KB_JWT_TYP,
  type KBOptions,
  type PresentationFrame,
  type SafeVerifyResult,
  SD_DECOY,
  SD_DIGEST,
  type SDJWTCompact,
  type SDJWTConfig,
  type Signer,
  type VerificationError,
  type VerificationErrorCode,
} from './types';
import {
  base64urlEncode,
  ensureError,
  SDJWTException,
  uint8ArrayToBase64Url,
} from './utils';
import { decodeBase64urlJsonStrict } from './utils/strict-json';

export * from './decode';
export * from './decoy';
export * from './flattenJSON';
export * from './generalJSON';
export * from './jwt';
export * from './kbjwt';
export * from './present';
export * from './sdjwt';
// Re-export all types, utils, decode, and present functionality
export * from './types';
export * from './utils';

export type SdJwtPayload = Record<string, unknown>;

export class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> {
  //header type
  protected type?: string;

  public static readonly DEFAULT_hashAlg = 'sha-256';

  protected userConfig: SDJWTConfig<T> = {};

  constructor(userConfig?: SDJWTConfig<T>) {
    if (userConfig) {
      if (
        userConfig.hashAlg &&
        !IANA_HASH_ALGORITHMS.includes(userConfig.hashAlg)
      ) {
        throw new SDJWTException(
          `Invalid hash algorithm: ${userConfig.hashAlg}`,
        );
      }
      this.userConfig = userConfig;
    }
  }

  private async createKBJwt(
    options: KBOptions,
    sdHash: string,
  ): Promise<KBJwt> {
    if (!this.userConfig.kbSigner) {
      throw new SDJWTException('Key Binding Signer not found');
    }
    if (!this.userConfig.kbSignAlg) {
      throw new SDJWTException('Key Binding sign algorithm not specified');
    }

    const { payload } = options;
    const kbJwt = new KBJwt({
      header: {
        typ: KB_JWT_TYP,
        alg: this.userConfig.kbSignAlg,
      },
      payload: { ...payload, sd_hash: sdHash },
    });

    await kbJwt.sign(this.userConfig.kbSigner);
    return kbJwt;
  }

  private async SignJwt(jwt: Jwt) {
    if (!this.userConfig.signer) {
      throw new SDJWTException('Signer not found');
    }
    await jwt.sign(this.userConfig.signer);
    return jwt;
  }

  private async VerifyJwt(jwt: Jwt, options?: T & VerifierOptions) {
    if (!this.userConfig.verifier) {
      throw new SDJWTException('Verifier not found');
    }
    return jwt.verify<T>(this.userConfig.verifier, options);
  }

  public async issue<Payload extends ExtendedPayload>(
    payload: Payload,
    disclosureFrame?: DisclosureFrame<Payload>,
    options?: {
      header?: object; // This is for customizing the header of the jwt
    },
  ): Promise<SDJWTCompact> {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }

    if (!this.userConfig.saltGenerator) {
      throw new SDJWTException('SaltGenerator not found');
    }

    if (!this.userConfig.signAlg) {
      throw new SDJWTException('sign alogrithm not specified');
    }

    this.validateReservedFields<Payload>(payload);
    this.validateDisclosureFrame<Payload>(disclosureFrame);

    const hasher = this.userConfig.hasher;
    const hashAlg = this.userConfig.hashAlg ?? SDJwtInstance.DEFAULT_hashAlg;

    const { packedClaims, disclosures } = await pack(
      payload,
      disclosureFrame,
      { hasher, alg: hashAlg },
      this.userConfig.saltGenerator,
    );
    const alg = this.userConfig.signAlg;
    const OptionHeader = options?.header ?? {};
    const CustomHeader = this.userConfig.omitTyp
      ? OptionHeader
      : { typ: this.type, ...OptionHeader };
    const header = { ...CustomHeader, alg };
    const jwt = new Jwt({
      header,
      payload: {
        ...packedClaims,
        _sd_alg: disclosureFrame ? hashAlg : undefined,
      },
    });
    await this.SignJwt(jwt);

    const sdJwt = new SDJwt({
      jwt,
      disclosures,
    });

    return sdJwt.encodeSDJwt();
  }

  /**
   * Validates if the payload contains any reserved claim names. If so it will throw an error.
   * @param payload
   * @returns
   */
  protected validateReservedFields<T extends ExtendedPayload>(payload: T) {
    const reservedFields = new Set([SD_DIGEST, '_sd_alg', SD_DECOY]);

    const visit = (node: unknown) => {
      if (!node || typeof node !== 'object') {
        return;
      }

      for (const [key, value] of Object.entries(
        node as Record<string, unknown>,
      )) {
        if (reservedFields.has(key)) {
          throw new SDJWTException(
            `Reserved field name "${key}" is not allowed`,
          );
        }
        visit(value);
      }
    };

    visit(payload);
  }

  protected validateDisclosureFrame<T extends ExtendedPayload>(
    _disclosureFrame?: DisclosureFrame<T>,
  ) {
    return;
  }

  public async present<T extends Record<string, unknown>>(
    encodedSDJwt: string,
    presentationFrame?: PresentationFrame<T>,
    options?: {
      kb?: KBOptions;
    },
  ): Promise<SDJWTCompact> {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const hasher = this.userConfig.hasher;

    const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);

    if (!sdjwt.jwt?.payload) throw new SDJWTException('Payload not found');
    const presentSdJwtWithoutKb = await sdjwt.present(
      presentationFrame,
      hasher,
    );

    if (!options?.kb) {
      return presentSdJwtWithoutKb;
    }

    const sdHashStr = await this.calculateSDHash(
      presentSdJwtWithoutKb,
      sdjwt,
      hasher,
    );

    sdjwt.kbJwt = await this.createKBJwt(options.kb, sdHashStr);
    return sdjwt.present(presentationFrame, hasher);
  }

  // This function is for verifying the SD JWT
  // If requiredClaimKeys is provided, it will check if the required claim keys are presentation in the SD JWT
  // If requireKeyBindings is true, it will check if the key binding JWT is presentation and verify it
  public async verify(encodedSDJwt: string, options?: T & VerifierOptions) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const hasher = this.userConfig.hasher;

    const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
    if (!sdjwt.jwt || !sdjwt.jwt.payload) {
      throw new SDJWTException('Invalid SD JWT');
    }
    const { payload, header } = await this.validate(encodedSDJwt, options);

    if (options?.requiredClaimKeys) {
      const keys = await sdjwt.keys(hasher);
      const missingKeys = options.requiredClaimKeys.filter(
        (k) => !keys.includes(k),
      );
      if (missingKeys.length > 0) {
        throw new SDJWTException(
          `Missing required claim keys: ${missingKeys.join(', ')}`,
        );
      }
    }

    if (!options?.keyBindingNonce) {
      return { payload, header };
    }

    if (!sdjwt.kbJwt) {
      throw new SDJWTException('Key Binding JWT not exist');
    }
    if (!this.userConfig.kbVerifier) {
      throw new SDJWTException('Key Binding Verifier not found');
    }
    const kb = await sdjwt.kbJwt.verifyKB({
      verifier: this.userConfig.kbVerifier,
      payload,
      nonce: options.keyBindingNonce,
      options,
    });
    if (!kb) {
      throw new Error('signature is not valid');
    }

    const sdHashfromKb = kb.payload.sd_hash;
    const sdjwtWithoutKb = new SDJwt({
      jwt: sdjwt.jwt,
      disclosures: sdjwt.disclosures,
    });

    const presentSdJwtWithoutKb = sdjwtWithoutKb.encodeSDJwt();
    const sdHashStr = await this.calculateSDHash(
      presentSdJwtWithoutKb,
      sdjwt,
      hasher,
    );

    if (sdHashStr !== sdHashfromKb) {
      throw new SDJWTException('Invalid sd_hash in Key Binding JWT');
    }

    return { payload, header, kb };
  }

  /**
   * Safe verification that collects all errors instead of failing fast.
   * Returns a result object with either the verified data or an array of all errors.
   *
   * @param encodedSDJwt - The encoded SD-JWT to verify
   * @param options - Verification options
   * @returns A SafeVerifyResult containing either success data or collected errors
   */
  public async safeVerify(
    encodedSDJwt: string,
    options?: T & VerifierOptions,
  ): Promise<
    SafeVerifyResult<{
      payload: ExtendedPayload;
      header: Record<string, unknown> | undefined;
      kb?: {
        payload: Record<string, unknown>;
        header: Record<string, unknown>;
      };
    }>
  > {
    const errors: VerificationError[] = [];

    // Helper to add errors
    const addError = (
      code: VerificationErrorCode,
      message: string,
      details?: unknown,
    ) => {
      errors.push({ code, message, details });
    };

    // Helper to convert exception to error code
    const exceptionToCode = (error: Error): VerificationErrorCode => {
      const message = error.message.toLowerCase();
      if (message.includes('hasher not found')) return 'HASHER_NOT_FOUND';
      if (message.includes('verifier not found')) return 'VERIFIER_NOT_FOUND';
      if (message.includes('invalid sd jwt') || message.includes('invalid jwt'))
        return 'INVALID_SD_JWT';
      if (message.includes('not yet valid')) return 'JWT_NOT_YET_VALID';
      if (message.includes('expired')) return 'JWT_EXPIRED';
      if (message.includes('signature')) return 'INVALID_JWT_SIGNATURE';
      if (message.includes('missing required claim'))
        return 'MISSING_REQUIRED_CLAIMS';
      if (message.includes('key binding jwt not exist'))
        return 'KEY_BINDING_JWT_MISSING';
      if (message.includes('key binding verifier not found'))
        return 'KEY_BINDING_VERIFIER_NOT_FOUND';
      if (message.includes('sd_hash')) return 'KEY_BINDING_SD_HASH_INVALID';
      return 'UNKNOWN_ERROR';
    };

    // Check basic configuration first
    if (!this.userConfig.hasher) {
      addError('HASHER_NOT_FOUND', 'Hasher not found');
    }
    if (!this.userConfig.verifier) {
      addError('VERIFIER_NOT_FOUND', 'Verifier not found');
    }

    // If basic config is missing, return early
    if (errors.length > 0) {
      return { success: false, errors };
    }

    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }

    // hasher and verifier are guaranteed to be defined here
    const hasher = this.userConfig.hasher;

    // Try to decode and validate the SD-JWT
    let sdjwt: SDJwt | undefined;
    let payload: ExtendedPayload | undefined;
    let header: Record<string, unknown> | undefined;

    try {
      sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
      if (!sdjwt.jwt || !sdjwt.jwt.payload) {
        addError('INVALID_SD_JWT', 'Invalid SD JWT: missing JWT or payload');
      }
    } catch (e) {
      const error = ensureError(e);
      addError(
        'INVALID_SD_JWT',
        `Failed to decode SD-JWT: ${error.message}`,
        error,
      );
    }

    // Validate signature and claims
    if (sdjwt?.jwt) {
      try {
        const result = await this.VerifyJwt(sdjwt.jwt, options);
        header = result.header;
        const claims = await sdjwt.getClaims(hasher);
        payload = claims as ExtendedPayload;
      } catch (e) {
        const error = ensureError(e);
        const code = exceptionToCode(error);
        addError(code, error.message, error);
      }
    }

    // Check required claim keys
    if (sdjwt && options?.requiredClaimKeys) {
      try {
        const keys = await sdjwt.keys(hasher);
        const missingKeys = options.requiredClaimKeys.filter(
          (k) => !keys.includes(k),
        );
        if (missingKeys.length > 0) {
          addError(
            'MISSING_REQUIRED_CLAIMS',
            `Missing required claim keys: ${missingKeys.join(', ')}`,
            { missingKeys },
          );
        }
      } catch (e) {
        const error = ensureError(e);
        addError(
          'UNKNOWN_ERROR',
          `Failed to check required claims: ${error.message}`,
          error,
        );
      }
    }

    // Verify key binding if requested
    let kb:
      | { payload: Record<string, unknown>; header: Record<string, unknown> }
      | undefined;
    if (options?.keyBindingNonce && sdjwt) {
      if (!sdjwt.kbJwt) {
        addError('KEY_BINDING_JWT_MISSING', 'Key Binding JWT not exist');
      } else if (!this.userConfig.kbVerifier) {
        addError(
          'KEY_BINDING_VERIFIER_NOT_FOUND',
          'Key Binding Verifier not found',
        );
      } else if (payload) {
        try {
          const kbResult = await sdjwt.kbJwt.verifyKB({
            verifier: this.userConfig.kbVerifier,
            payload,
            nonce: options.keyBindingNonce,
            options,
          });
          if (!kbResult) {
            addError(
              'KEY_BINDING_SIGNATURE_INVALID',
              'Key binding signature is not valid',
            );
          } else {
            kb = kbResult;

            // Verify sd_hash
            const sdjwtWithoutKb = new SDJwt({
              jwt: sdjwt.jwt,
              disclosures: sdjwt.disclosures,
            });
            const presentSdJwtWithoutKb = sdjwtWithoutKb.encodeSDJwt();
            const sdHashStr = await this.calculateSDHash(
              presentSdJwtWithoutKb,
              sdjwt,
              hasher,
            );

            if (sdHashStr !== kbResult.payload.sd_hash) {
              addError(
                'KEY_BINDING_SD_HASH_INVALID',
                'Invalid sd_hash in Key Binding JWT',
                {
                  expected: sdHashStr,
                  received: kbResult.payload.sd_hash,
                },
              );
            }
          }
        } catch (e) {
          const error = ensureError(e);
          addError(
            'KEY_BINDING_SIGNATURE_INVALID',
            `Key binding verification failed: ${error.message}`,
            error,
          );
        }
      }
    }

    // Return result
    if (errors.length > 0) {
      return { success: false, errors };
    }

    return {
      success: true,
      data: {
        payload: payload as ExtendedPayload,
        header,
        kb,
      },
    };
  }

  private async calculateSDHash(
    presentSdJwtWithoutKb: string,
    sdjwt: SDJwt,
    hasher: Hasher,
  ) {
    if (!sdjwt.jwt || !sdjwt.jwt.payload) {
      throw new SDJWTException('Invalid SD JWT');
    }
    const { _sd_alg } = getSDAlgAndPayload(sdjwt.jwt.payload);
    const sdHash = await hasher(presentSdJwtWithoutKb, _sd_alg);
    const sdHashStr = uint8ArrayToBase64Url(sdHash);
    return sdHashStr;
  }

  /**
   * This function is for validating the SD JWT
   * Checking signature, if provided the iat and exp when provided and return its the claims
   * @param encodedSDJwt
   * @param options
   * @returns
   */
  public async validate(encodedSDJwt: string, options?: T & VerifierOptions) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const hasher = this.userConfig.hasher;

    const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
    if (!sdjwt.jwt) {
      throw new SDJWTException('Invalid SD JWT');
    }

    const verifiedPayloads = await this.VerifyJwt(sdjwt.jwt, options);
    const claims = await sdjwt.getClaims<ExtendedPayload>(hasher);
    return { payload: claims, header: verifiedPayloads.header };
  }

  public config(newConfig: SDJWTConfig) {
    this.userConfig = { ...this.userConfig, ...newConfig };
  }

  public encode(sdJwt: SDJwt): SDJWTCompact {
    return sdJwt.encodeSDJwt();
  }

  public decode(endcodedSDJwt: SDJWTCompact) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    return SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
  }

  public async keys(endcodedSDJwt: SDJWTCompact) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
    return sdjwt.keys(this.userConfig.hasher);
  }

  public async presentableKeys(endcodedSDJwt: SDJWTCompact) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
    return sdjwt.presentableKeys(this.userConfig.hasher);
  }

  public async getClaims(endcodedSDJwt: SDJWTCompact) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
    return sdjwt.getClaims(this.userConfig.hasher);
  }

  public toFlattenJSON(endcodedSDJwt: SDJWTCompact) {
    return FlattenJSON.fromEncode(endcodedSDJwt);
  }

  public toGeneralJSON(endcodedSDJwt: SDJWTCompact) {
    return GeneralJSON.fromEncode(endcodedSDJwt);
  }
}

export class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> {
  //header type
  protected type?: string;

  public static readonly DEFAULT_hashAlg = 'sha-256';

  protected userConfig: SDJWTConfig = {};

  constructor(userConfig?: SDJWTConfig) {
    if (userConfig) {
      if (
        userConfig.hashAlg &&
        !IANA_HASH_ALGORITHMS.includes(userConfig.hashAlg)
      ) {
        throw new SDJWTException(
          `Invalid hash algorithm: ${userConfig.hashAlg}`,
        );
      }
      this.userConfig = userConfig;
    }
  }

  private async createKBJwt(
    options: KBOptions,
    sdHash: string,
  ): Promise<KBJwt> {
    if (!this.userConfig.kbSigner) {
      throw new SDJWTException('Key Binding Signer not found');
    }
    if (!this.userConfig.kbSignAlg) {
      throw new SDJWTException('Key Binding sign algorithm not specified');
    }

    const { payload } = options;
    const kbJwt = new KBJwt({
      header: {
        typ: KB_JWT_TYP,
        alg: this.userConfig.kbSignAlg,
      },
      payload: { ...payload, sd_hash: sdHash },
    });

    await kbJwt.sign(this.userConfig.kbSigner);
    return kbJwt;
  }

  private encodeObj(obj: Record<string, unknown>): string {
    return base64urlEncode(JSON.stringify(obj));
  }

  public async issue<Payload extends ExtendedPayload>(
    payload: Payload,
    disclosureFrame: DisclosureFrame<Payload> | undefined,
    options: {
      sigs: Array<{
        signer: Signer;
        alg: string;
        kid: string;
        header?: Record<string, unknown>;
      }>; // multiple signers for the credential
    },
  ): Promise<GeneralJSON> {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }

    if (!this.userConfig.saltGenerator) {
      throw new SDJWTException('SaltGenerator not found');
    }

    this.validateReservedFields<Payload>(payload);
    this.validateDisclosureFrame<Payload>(disclosureFrame);

    const hasher = this.userConfig.hasher;
    const hashAlg = this.userConfig.hashAlg ?? SDJwtInstance.DEFAULT_hashAlg;

    const { packedClaims, disclosures } = await pack(
      payload,
      disclosureFrame,
      { hasher, alg: hashAlg },
      this.userConfig.saltGenerator,
    );

    const encodedSDJwtPayload = this.encodeObj({
      ...packedClaims,
      _sd_alg: disclosureFrame ? hashAlg : undefined,
    });

    const encodedDisclosures = disclosures.map((disclosure) =>
      disclosure.encode(),
    );

    const signatures = await Promise.all(
      options.sigs.map(async (s) => {
        const { signer, alg, kid, header } = s;
        const protectedHeader = { typ: this.type, alg, kid, ...header };
        const encodedProtectedHeader = this.encodeObj(protectedHeader);
        const signature = await signer(
          `${encodedProtectedHeader}.${encodedSDJwtPayload}`,
        );

        return {
          protected: encodedProtectedHeader,
          signature,
        };
      }),
    );

    const generalJson = new GeneralJSON({
      payload: encodedSDJwtPayload,
      disclosures: encodedDisclosures,
      signatures,
    });

    return generalJson;
  }

  /**
   * Validates if the payload contains any reserved claim names. If so it will throw an error.
   * @param payload
   * @returns
   */
  protected validateReservedFields<T extends ExtendedPayload>(payload: T) {
    const reservedFields = new Set([SD_DIGEST, '_sd_alg', SD_DECOY]);

    const visit = (node: unknown) => {
      if (!node || typeof node !== 'object') {
        return;
      }

      for (const [key, value] of Object.entries(
        node as Record<string, unknown>,
      )) {
        if (reservedFields.has(key)) {
          throw new SDJWTException(
            `Reserved field name "${key}" is not allowed`,
          );
        }
        visit(value);
      }
    };

    visit(payload);
  }

  protected validateDisclosureFrame<T extends ExtendedPayload>(
    _disclosureFrame?: DisclosureFrame<T>,
  ) {
    return;
  }

  public async present<T extends Record<string, unknown>>(
    generalJSON: GeneralJSON,
    presentationFrame?: PresentationFrame<T>,
    options?: {
      kb?: KBOptions;
    },
  ): Promise<GeneralJSON> {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const hasher = this.userConfig.hasher;
    const encodedSDJwt = generalJSON.toEncoded(0);
    const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);

    if (!sdjwt.jwt?.payload) throw new SDJWTException('Payload not found');
    const disclosures = await sdjwt.getPresentDisclosures(
      presentationFrame,
      hasher,
    );
    const encodedDisclosures = disclosures.map((d) => d.encode());
    const presentedGeneralJSON = new GeneralJSON({
      payload: generalJSON.payload,
      disclosures: encodedDisclosures,
      signatures: generalJSON.signatures,
    });

    if (!options?.kb) {
      return presentedGeneralJSON;
    }

    const presentSdJwtWithoutKb = await sdjwt.present(
      presentationFrame,
      hasher,
    );

    const sdHashStr = await this.calculateSDHash(
      presentSdJwtWithoutKb,
      sdjwt,
      hasher,
    );

    const kbJwt = await this.createKBJwt(options.kb, sdHashStr);
    const encodedKbJwt = kbJwt.encodeJwt();
    presentedGeneralJSON.kb_jwt = encodedKbJwt;
    return presentedGeneralJSON;
  }

  // This function is for verifying the SD JWT
  // If requiredClaimKeys is provided, it will check if the required claim keys are presentation in the SD JWT
  // If requireKeyBindings is true, it will check if the key binding JWT is presentation and verify it
  public async verify(generalJSON: GeneralJSON, options?: VerifierOptions) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const hasher = this.userConfig.hasher;

    const { payload, headers } = await this.validate(generalJSON);

    const encodedSDJwt = generalJSON.toEncoded(0);
    const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
    if (!sdjwt.jwt || !sdjwt.jwt.payload) {
      throw new SDJWTException('Invalid SD JWT');
    }

    if (options?.requiredClaimKeys) {
      const keys = await sdjwt.keys(hasher);
      const missingKeys = options?.requiredClaimKeys.filter(
        (k) => !keys.includes(k),
      );
      if (missingKeys.length > 0) {
        throw new SDJWTException(
          `Missing required claim keys: ${missingKeys.join(', ')}`,
        );
      }
    }

    if (!options?.keyBindingNonce) {
      return { payload, headers };
    }

    if (!sdjwt.kbJwt) {
      throw new SDJWTException('Key Binding JWT not exist');
    }
    if (!this.userConfig.kbVerifier) {
      throw new SDJWTException('Key Binding Verifier not found');
    }
    const kb = await sdjwt.kbJwt.verifyKB({
      verifier: this.userConfig.kbVerifier,
      payload,
      nonce: options.keyBindingNonce,
      options,
    });
    if (!kb) {
      throw new Error('signature is not valid');
    }
    const sdHashfromKb = kb.payload.sd_hash;
    const sdjwtWithoutKb = new SDJwt({
      jwt: sdjwt.jwt,
      disclosures: sdjwt.disclosures,
    });

    const presentSdJwtWithoutKb = sdjwtWithoutKb.encodeSDJwt();
    const sdHashStr = await this.calculateSDHash(
      presentSdJwtWithoutKb,
      sdjwt,
      hasher,
    );

    if (sdHashStr !== sdHashfromKb) {
      throw new SDJWTException('Invalid sd_hash in Key Binding JWT');
    }

    return { payload, headers, kb };
  }

  private async calculateSDHash(
    presentSdJwtWithoutKb: string,
    sdjwt: SDJwt,
    hasher: Hasher,
  ) {
    if (!sdjwt.jwt || !sdjwt.jwt.payload) {
      throw new SDJWTException('Invalid SD JWT');
    }
    const { _sd_alg } = getSDAlgAndPayload(sdjwt.jwt.payload);
    const sdHash = await hasher(presentSdJwtWithoutKb, _sd_alg);
    const sdHashStr = uint8ArrayToBase64Url(sdHash);
    return sdHashStr;
  }

  // This function is for validating the SD JWT
  // Just checking signature and return its the claims
  public async validate(generalJSON: GeneralJSON) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    if (!this.userConfig.verifier) {
      throw new SDJWTException('Verifier not found');
    }
    const hasher = this.userConfig.hasher;
    const verifier = this.userConfig.verifier;

    const { payload, signatures } = generalJSON;

    const results = await Promise.all(
      signatures.map(async (s) => {
        const { protected: encodedHeader, signature } = s;
        const verified = await verifier(
          `${encodedHeader}.${payload}`,
          signature,
        );
        const header = decodeBase64urlJsonStrict(encodedHeader, 'Invalid JWT');
        return { verified, header };
      }),
    );

    const verified = results.every((r) => r.verified);
    if (!verified) {
      throw new SDJWTException('Signature is not valid');
    }

    const encodedSDJwt = generalJSON.toEncoded(0);
    const sdjwt = await SDJwt.fromEncode(encodedSDJwt, hasher);
    if (!sdjwt.jwt) {
      throw new SDJWTException('Invalid SD JWT');
    }

    const claims = await sdjwt.getClaims<ExtendedPayload>(hasher);
    return { payload: claims, headers: results.map((r) => r.header) };
  }

  public config(newConfig: SDJWTConfig) {
    this.userConfig = { ...this.userConfig, ...newConfig };
  }

  public encode(sdJwt: GeneralJSON, index: number): SDJWTCompact {
    return sdJwt.toEncoded(index);
  }

  public decode(endcodedSDJwt: SDJWTCompact) {
    return GeneralJSON.fromEncode(endcodedSDJwt);
  }

  public async keys(generalSdjwt: GeneralJSON) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const endcodedSDJwt = generalSdjwt.toEncoded(0);
    const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
    return sdjwt.keys(this.userConfig.hasher);
  }

  public async presentableKeys(generalSdjwt: GeneralJSON) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const endcodedSDJwt = generalSdjwt.toEncoded(0);
    const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
    return sdjwt.presentableKeys(this.userConfig.hasher);
  }

  public async getClaims(generalSdjwt: GeneralJSON) {
    if (!this.userConfig.hasher) {
      throw new SDJWTException('Hasher not found');
    }
    const endcodedSDJwt = generalSdjwt.toEncoded(0);
    const sdjwt = await SDJwt.fromEncode(endcodedSDJwt, this.userConfig.hasher);
    return sdjwt.getClaims(this.userConfig.hasher);
  }
}
