// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/webid/delegation/federated_sd_jwt_handler.h"

#include <optional>
#include <string>
#include <vector>

#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/json/json_reader.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "base/values.h"
#include "content/browser/webid/delegation/jwt_signer.h"
#include "content/browser/webid/delegation/sd_jwt.h"
#include "content/browser/webid/flags.h"
#include "content/browser/webid/mappers.h"
#include "content/browser/webid/request.h"
#include "crypto/hash.h"
#include "crypto/keypair.h"
#include "crypto/sha2.h"
#include "third_party/blink/public/mojom/webid/federated_request.mojom.h"

namespace content {

using blink::mojom::FederatedRequestResult;

namespace {
std::vector<uint8_t> Sha256(std::string_view data) {
  auto hash = crypto::hash::Sha256(base::as_byte_span(data));
  std::vector<uint8_t> result{hash.begin(), hash.end()};
  return result;
}
}  // namespace

FederatedSdJwtHandler::FederatedSdJwtHandler(
    const blink::mojom::IdentityProviderRequestOptionsPtr& provider,
    RenderFrameHost& render_frame_host,
    webid::Request* request)
    : fields_(provider->fields),
      nonce_(provider->nonce),
      config_url_(provider->config->config_url),
      render_frame_host_(&render_frame_host),
      request_(request) {
  // Creates a throw away private key for a one-time use for
  // a single presentation. The public key gets sent to the
  // VC issuance endpoint and gets bound to the issued SD-JWT
  // by the issuer, delegating the presentation to the holder.
  // The browser selectively discloses the fields that were
  // requested and binds the audience and the nonce to the
  // Key Binding JWT before returning to the verifier.
  private_key_ = crypto::keypair::PrivateKey::GenerateEcP256();
}

FederatedSdJwtHandler::~FederatedSdJwtHandler() {}

std::string FederatedSdJwtHandler::ComputeUrlEncodedTokenPostDataForIssuers(
    const std::string& account_id) {
  return base::StrCat(
      {"account_id=", base::EscapeUrlEncodedData(account_id, /*use_plus=*/true),
       "&holder_key=",
       base::EscapeUrlEncodedData(*GetPublicKey().Serialize(),
                                  /*use_plus=*/true),
       "&format=", base::EscapeUrlEncodedData("vc+sd-jwt", /*use_plus=*/true)});
}

void FederatedSdJwtHandler::ProcessSdJwt(const std::string& token) {
  // Checked previously.
  DCHECK(webid::IsDelegationEnabled());

  auto value = sdjwt::SdJwt::Parse(token);
  if (!value) {
    request_->CompleteRequestWithError(FederatedRequestResult::kError,
                                       /*token_status=*/std::nullopt,
                                       /*should_delay_callback=*/false);
    return;
  }

  auto sd_jwt = sdjwt::SdJwt::From(*value);
  if (!sd_jwt) {
    request_->CompleteRequestWithError(FederatedRequestResult::kError,
                                       /*token_status=*/std::nullopt,
                                       /*should_delay_callback=*/false);
    return;
  }

  for (const auto& json : sd_jwt->disclosures) {
    std::optional<base::ListValue> result =
        base::JSONReader::ReadList(json.value(), base::JSON_PARSE_RFC);
    if (!result) {
      continue;
    }

    auto disclosure = sdjwt::Disclosure::From(*result);
    if (!disclosure) {
      continue;
    }

    disclosures_.push_back({disclosure->name, sdjwt::JSONString(json.value())});
  }

  OnSdJwtParsed(sd_jwt->jwt);
}

sdjwt::Jwk FederatedSdJwtHandler::GetPublicKey() const {
  return *sdjwt::ExportPublicKey(*private_key_);
}

void FederatedSdJwtHandler::OnSdJwtParsed(const sdjwt::Jwt& jwt) {
  std::vector<std::string> fields = {webid::kDefaultFieldName,
                                     webid::kDefaultFieldEmail,
                                     webid::kDefaultFieldPicture};
  if (fields_.has_value()) {
    fields = fields_.value();
  }

  auto selected = sdjwt::SdJwt::Disclose(disclosures_, fields);

  disclosures_.clear();

  if (!selected) {
    request_->CompleteRequestWithError(FederatedRequestResult::kError,
                                       /*token_status=*/std::nullopt,
                                       /*should_delay_callback=*/false);
    return;
  }

  sdjwt::SdJwt result;
  result.jwt = jwt;
  result.disclosures = *selected;

  auto sdjwtkb = sdjwt::SdJwtKb::Create(
      result, render_frame_host_->GetLastCommittedOrigin().Serialize(), nonce_,
      /*iat=*/base::Time::Now(), base::BindRepeating(Sha256),
      sdjwt::CreateJwtSigner(*std::move(private_key_)));

  if (!sdjwtkb) {
    request_->CompleteRequestWithError(FederatedRequestResult::kError,
                                       /*token_status=*/std::nullopt,
                                       /*should_delay_callback=*/false);
    return;
  }

  auto token = sdjwtkb->Serialize();
  // TODO(crbug.com/380367784): introduce and use a more specific
  // TokenStatus type for SD-JWTs.
  request_->CompleteRequest(
      FederatedRequestResult::kSuccess,
      webid::RequestIdTokenStatus::kSuccessUsingTokenInHttpResponse,
      /*token_error=*/std::nullopt, config_url_, base::Value(token),
      /*should_delay_callback=*/false);
}

}  // namespace content
