module WebauthnTestHelper
  # Fixed EC P-256 key pair for WebAuthn tests.
  WEBAUTHN_PRIVATE_KEY = OpenSSL::PKey::EC.new(
    [ "307702010104201dd589de7210b3318620f32150e3012cce021519df1d6e9e01" \
      "0471146d395cdca00a06082a8648ce3d030107a14403420004116847fe19e1ad" \
      "4471ab9980d7ff9cc1e4c7cb7a3af00e082b64fcd84f5ae70114c2495eef16f" \
      "542b5e57dd1b263661624e3cf28f581b57a441edbd756a41d0e" ].pack("H*")
  )

  # Pre-encoded COSE EC2/ES256 public key (CBOR) for the key above.
  # {1: 2, 3: -7, -1: 1, -2: <x 32 bytes>, -3: <y 32 bytes>}
  COSE_PUBLIC_KEY = [ "a5010203262001215820116847fe19e1ad4471ab9980d7ff9cc1" \
    "e4c7cb7a3af00e082b64fcd84f5ae70122582014c2495eef16f542b5e57dd1b2" \
    "63661624e3cf28f581b57a441edbd756a41d0e" ].pack("H*")

  # CBOR prefix for {"fmt": "none", "attStmt": {}, "authData": bytes(164)}.
  # Auth data is always 164 bytes: rp_id_hash(32) + flags(1) + sign_count(4)
  # + aaguid(16) + credential_id_length(2) + credential_id(32) + cose_key(77).
  ATTESTATION_OBJECT_CBOR_PREFIX =
    [ "a363666d74646e6f6e656761747453746d74a068617574684461746158a4" ].pack("H*")

  private
    def request_webauthn_challenge(purpose: "authentication")
      untenanted { post my_passkey_challenge_url, params: { purpose: purpose } }
      response.parsed_body["challenge"]
    end

    def webauthn_challenge(purpose: nil)
      ActionPack::WebAuthn::PublicKeyCredential::Options.new(challenge_purpose: purpose).challenge
    end

    def webauthn_private_key
      WEBAUTHN_PRIVATE_KEY
    end

    def build_attestation_params(challenge:)
      credential_id = SecureRandom.random_bytes(32)
      auth_data = build_attestation_auth_data(credential_id: credential_id)

      {
        passkey: {
          client_data_json: webauthn_client_data_json(challenge: challenge, type: "webauthn.create"),
          attestation_object: Base64.urlsafe_encode64(ATTESTATION_OBJECT_CBOR_PREFIX + auth_data, padding: false),
          transports: [ "internal" ]
        }
      }
    end

    def build_assertion_params(challenge:, credential:, sign_count: 1)
      client_data_json = webauthn_client_data_json(challenge: challenge, type: "webauthn.get")
      authenticator_data = build_assertion_auth_data(sign_count: sign_count)
      signature = webauthn_sign(authenticator_data, client_data_json)

      {
        passkey: {
          id: credential.credential_id,
          client_data_json: client_data_json,
          authenticator_data: Base64.urlsafe_encode64(authenticator_data, padding: false),
          signature: Base64.urlsafe_encode64(signature, padding: false)
        }
      }
    end

    def webauthn_client_data_json(challenge:, type:)
      { challenge: challenge, origin: "http://www.example.com", type: type }.to_json
    end

    # Attestation auth data includes the attested credential (public key + credential ID).
    def build_attestation_auth_data(credential_id:)
      [
        Digest::SHA256.digest("www.example.com"),   # rp_id_hash
        [ 0x45 ].pack("C"),                         # flags: UP + UV + AT
        [ 0 ].pack("N"),                            # sign_count
        "\x00" * 16,                                # aaguid
        [ credential_id.bytesize ].pack("n"),       # credential_id_length
        credential_id,
        COSE_PUBLIC_KEY
      ].join.b
    end

    # Assertion auth data is simpler — no attested credential.
    def build_assertion_auth_data(sign_count:)
      [
        Digest::SHA256.digest("www.example.com"),
        [ 0x05 ].pack("C"),                         # flags: UP + UV
        [ sign_count ].pack("N")
      ].join.b
    end

    def webauthn_sign(authenticator_data, client_data_json)
      signed_data = authenticator_data + Digest::SHA256.digest(client_data_json)
      WEBAUTHN_PRIVATE_KEY.sign("SHA256", signed_data)
    end
end
