// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package storage

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"hash/crc32"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"

	"cloud.google.com/go/auth"
	"cloud.google.com/go/iam/apiv1/iampb"
	"cloud.google.com/go/internal/optional"
	"github.com/google/uuid"
	"github.com/googleapis/gax-go/v2/callctx"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/iterator"
	"google.golang.org/api/option"
	"google.golang.org/api/option/internaloption"
	raw "google.golang.org/api/storage/v1"
	htransport "google.golang.org/api/transport/http"
)

// httpStorageClient is the HTTP-JSON API implementation of the transport-agnostic
// storageClient interface.
//
// TODO(b/498422946): Add client feature tracker in HTTP client.
type httpStorageClient struct {
	creds                      *auth.Credentials
	hc                         *http.Client
	xmlHost                    string
	raw                        *raw.Service
	scheme                     string
	settings                   *settings
	config                     *storageConfig
	dynamicReadReqStallTimeout *bucketDelayManager
}

// newHTTPStorageClient initializes a new storageClient that uses the HTTP-JSON
// Storage API.
func newHTTPStorageClient(ctx context.Context, opts ...storageOption) (storageClient, error) {
	s := initSettings(opts...)
	o := s.clientOption
	config := newStorageConfig(o...)

	var creds *auth.Credentials
	// In general, it is recommended to use raw.NewService instead of htransport.NewClient
	// since raw.NewService configures the correct default endpoints when initializing the
	// internal http client. However, in our case, "NewRangeReader" in reader.go needs to
	// access the http client directly to make requests, so we create the client manually
	// here so it can be re-used by both reader.go and raw.NewService. This means we need to
	// manually configure the default endpoint options on the http client. Furthermore, we
	// need to account for STORAGE_EMULATOR_HOST override when setting the default endpoints.
	if host := os.Getenv("STORAGE_EMULATOR_HOST"); host == "" {
		// Prepend default options to avoid overriding options passed by the user.
		o = append([]option.ClientOption{option.WithScopes(ScopeFullControl, "https://www.googleapis.com/auth/cloud-platform"), option.WithUserAgent(userAgent)}, o...)

		o = append(o, internaloption.WithDefaultEndpointTemplate("https://storage.UNIVERSE_DOMAIN/storage/v1/"),
			internaloption.WithDefaultMTLSEndpoint("https://storage.mtls.googleapis.com/storage/v1/"),
			internaloption.WithDefaultUniverseDomain("googleapis.com"),
		)
		// Don't error out here. The user may have passed in their own HTTP
		// client which does not auth with ADC or other common conventions.
		c, err := internaloption.AuthCreds(ctx, o)
		if err == nil {
			creds = c
			o = append(o, option.WithAuthCredentials(creds))
		}
	} else {
		var hostURL *url.URL

		if strings.Contains(host, "://") {
			h, err := url.Parse(host)
			if err != nil {
				return nil, err
			}
			hostURL = h
		} else {
			// Add scheme for user if not supplied in STORAGE_EMULATOR_HOST
			// URL is only parsed correctly if it has a scheme, so we build it ourselves
			hostURL = &url.URL{Scheme: "http", Host: host}
		}

		hostURL.Path = "storage/v1/"
		endpoint := hostURL.String()

		// Append the emulator host as default endpoint for the user
		o = append([]option.ClientOption{option.WithoutAuthentication()}, o...)

		o = append(o, internaloption.WithDefaultEndpointTemplate(endpoint))
		o = append(o, internaloption.WithDefaultMTLSEndpoint(endpoint))
	}
	s.clientOption = o

	// htransport selects the correct endpoint among WithEndpoint (user override), WithDefaultEndpointTemplate, and WithDefaultMTLSEndpoint.
	hc, ep, err := htransport.NewClient(ctx, s.clientOption...)
	if err != nil {
		return nil, fmt.Errorf("dialing: %w", err)
	}
	// RawService should be created with the chosen endpoint to take account of user override.
	rawService, err := raw.NewService(ctx, option.WithEndpoint(ep), option.WithHTTPClient(hc))
	if err != nil {
		return nil, fmt.Errorf("storage client: %w", err)
	}
	// Update xmlHost and scheme with the chosen endpoint.
	u, err := url.Parse(ep)
	if err != nil {
		return nil, fmt.Errorf("supplied endpoint %q is not valid: %w", ep, err)
	}

	var bd *bucketDelayManager
	if config.readStallTimeoutConfig != nil {
		drrstConfig := config.readStallTimeoutConfig
		bd, err = newBucketDelayManager(
			drrstConfig.TargetPercentile,
			getDynamicReadReqIncreaseRateFromEnv(),
			getDynamicReadReqInitialTimeoutSecFromEnv(drrstConfig.Min),
			drrstConfig.Min,
			defaultDynamicReqdReqMaxTimeout)
		if err != nil {
			return nil, fmt.Errorf("creating dynamic-delay: %w", err)
		}
	}

	return &httpStorageClient{
		creds:                      creds,
		hc:                         hc,
		xmlHost:                    u.Host,
		raw:                        rawService,
		scheme:                     u.Scheme,
		settings:                   s,
		config:                     &config,
		dynamicReadReqStallTimeout: bd,
	}, nil
}

func (c *httpStorageClient) Close() error {
	c.hc.CloseIdleConnections()
	return nil
}

// Top-level methods.

func (c *httpStorageClient) GetServiceAccount(ctx context.Context, project string, opts ...storageOption) (string, error) {
	s := callSettings(c.settings, opts...)
	call := c.raw.Projects.ServiceAccount.Get(project)
	var res *raw.ServiceAccount
	err := run(ctx, func(ctx context.Context) error {
		var err error
		res, err = call.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
	if err != nil {
		return "", err
	}
	return res.EmailAddress, nil
}

func (c *httpStorageClient) CreateBucket(ctx context.Context, project, bucket string, attrs *BucketAttrs, enableObjectRetention *bool, opts ...storageOption) (*BucketAttrs, error) {
	s := callSettings(c.settings, opts...)
	var bkt *raw.Bucket
	if attrs != nil {
		bkt = attrs.toRawBucket()
	} else {
		bkt = &raw.Bucket{}
	}
	bkt.Name = bucket
	// If there is lifecycle information but no location, explicitly set
	// the location. This is a GCS quirk/bug.
	if bkt.Location == "" && bkt.Lifecycle != nil {
		bkt.Location = "US"
	}
	req := c.raw.Buckets.Insert(project, bkt)
	if attrs != nil && attrs.PredefinedACL != "" {
		req.PredefinedAcl(attrs.PredefinedACL)
	}
	if attrs != nil && attrs.PredefinedDefaultObjectACL != "" {
		req.PredefinedDefaultObjectAcl(attrs.PredefinedDefaultObjectACL)
	}
	if enableObjectRetention != nil {
		req.EnableObjectRetention(*enableObjectRetention)
	}
	var battrs *BucketAttrs
	err := run(ctx, func(ctx context.Context) error {
		b, err := req.Context(ctx).Do()
		if err != nil {
			return err
		}
		battrs, err = newBucket(b)
		return err
	}, s.retry, s.idempotent)
	return battrs, err
}

func (c *httpStorageClient) ListBuckets(ctx context.Context, project string, opts ...storageOption) *BucketIterator {
	s := callSettings(c.settings, opts...)
	it := &BucketIterator{
		ctx:       ctx,
		projectID: project,
	}

	fetch := func(pageSize int, pageToken string) (token string, err error) {
		req := c.raw.Buckets.List(it.projectID)
		req.Projection("full")
		req.Prefix(it.Prefix)
		req.PageToken(pageToken)
		req.ReturnPartialSuccess(it.ReturnPartialSuccess)
		if pageSize > 0 {
			req.MaxResults(int64(pageSize))
		}
		var resp *raw.Buckets
		err = run(it.ctx, func(ctx context.Context) error {
			resp, err = req.Context(ctx).Do()
			return err
		}, s.retry, s.idempotent)
		if err != nil {
			return "", err
		}
		for _, item := range resp.Items {
			b, err := newBucket(item)
			if err != nil {
				return "", err
			}
			it.buckets = append(it.buckets, b)
		}
		it.unreachable = resp.Unreachable
		return resp.NextPageToken, nil
	}

	it.pageInfo, it.nextFunc = iterator.NewPageInfo(
		fetch,
		func() int { return len(it.buckets) },
		func() interface{} { b := it.buckets; it.buckets = nil; return b })

	return it
}

// Bucket methods.

func (c *httpStorageClient) DeleteBucket(ctx context.Context, bucket string, conds *BucketConditions, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)
	req := c.raw.Buckets.Delete(bucket)
	if err := applyBucketConds("httpStorageClient.DeleteBucket", conds, req); err != nil {
		return err
	}
	if s.userProject != "" {
		req.UserProject(s.userProject)
	}

	return run(ctx, func(ctx context.Context) error { return req.Context(ctx).Do() }, s.retry, s.idempotent)
}

func (c *httpStorageClient) GetBucket(ctx context.Context, bucket string, conds *BucketConditions, opts ...storageOption) (*BucketAttrs, error) {
	s := callSettings(c.settings, opts...)
	req := c.raw.Buckets.Get(bucket).Projection("full")
	err := applyBucketConds("httpStorageClient.GetBucket", conds, req)
	if err != nil {
		return nil, err
	}
	if s.userProject != "" {
		req.UserProject(s.userProject)
	}

	var resp *raw.Bucket
	err = run(ctx, func(ctx context.Context) error {
		resp, err = req.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)

	if err != nil {
		return nil, formatBucketError(err)
	}
	return newBucket(resp)
}
func (c *httpStorageClient) UpdateBucket(ctx context.Context, bucket string, uattrs *BucketAttrsToUpdate, conds *BucketConditions, opts ...storageOption) (*BucketAttrs, error) {
	s := callSettings(c.settings, opts...)
	rb := uattrs.toRawBucket()
	req := c.raw.Buckets.Patch(bucket, rb).Projection("full")
	err := applyBucketConds("httpStorageClient.UpdateBucket", conds, req)
	if err != nil {
		return nil, err
	}
	if s.userProject != "" {
		req.UserProject(s.userProject)
	}
	if uattrs != nil && uattrs.PredefinedACL != "" {
		req.PredefinedAcl(uattrs.PredefinedACL)
	}
	if uattrs != nil && uattrs.PredefinedDefaultObjectACL != "" {
		req.PredefinedDefaultObjectAcl(uattrs.PredefinedDefaultObjectACL)
	}

	var rawBucket *raw.Bucket
	err = run(ctx, func(ctx context.Context) error {
		rawBucket, err = req.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
	if err != nil {
		return nil, err
	}
	return newBucket(rawBucket)
}

func (c *httpStorageClient) LockBucketRetentionPolicy(ctx context.Context, bucket string, conds *BucketConditions, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)

	var metageneration int64
	if conds != nil {
		metageneration = conds.MetagenerationMatch
	}
	req := c.raw.Buckets.LockRetentionPolicy(bucket, metageneration)

	return run(ctx, func(ctx context.Context) error {
		_, err := req.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
}

func (c *httpStorageClient) ListObjects(ctx context.Context, bucket string, q *Query, opts ...storageOption) *ObjectIterator {
	s := callSettings(c.settings, opts...)
	it := &ObjectIterator{
		ctx: ctx,
	}
	if q != nil {
		it.query = *q
	}
	fetch := func(pageSize int, pageToken string) (string, error) {
		var err error
		// Add trace span around List API call within the fetch.
		ctx, _ = startSpan(ctx, "httpStorageClient.ObjectsListCall")
		defer func() { endSpan(ctx, err) }()
		req := c.raw.Objects.List(bucket)
		if it.query.SoftDeleted {
			req.SoftDeleted(it.query.SoftDeleted)
		}
		projection := it.query.Projection
		if projection == ProjectionDefault {
			projection = ProjectionFull
		}
		req.Projection(projection.String())
		req.Delimiter(it.query.Delimiter)
		req.Prefix(it.query.Prefix)
		req.StartOffset(it.query.StartOffset)
		req.EndOffset(it.query.EndOffset)
		req.Versions(it.query.Versions)
		req.IncludeTrailingDelimiter(it.query.IncludeTrailingDelimiter)
		req.MatchGlob(it.query.MatchGlob)
		req.IncludeFoldersAsPrefixes(it.query.IncludeFoldersAsPrefixes)

		// Cannot pass empty filter
		if it.query.Filter != "" {
			req.Filter(it.query.Filter)
		}

		if selection := it.query.toFieldSelection(); selection != "" {
			req.Fields("nextPageToken", googleapi.Field(selection))
		}
		req.PageToken(pageToken)
		if s.userProject != "" {
			req.UserProject(s.userProject)
		}
		if pageSize > 0 {
			req.MaxResults(int64(pageSize))
		}
		var resp *raw.Objects
		err = run(it.ctx, func(ctx context.Context) error {
			resp, err = req.Context(ctx).Do()
			return err
		}, s.retry, s.idempotent, withOperation("ListObjects"), withObject(it.query.Prefix), withBucket(bucket))
		if err != nil {
			return "", formatBucketError(err)
		}
		for _, item := range resp.Items {
			it.items = append(it.items, newObject(item))
		}
		for _, prefix := range resp.Prefixes {
			it.items = append(it.items, &ObjectAttrs{Prefix: prefix})
		}
		return resp.NextPageToken, nil
	}
	it.pageInfo, it.nextFunc = iterator.NewPageInfo(
		fetch,
		func() int { return len(it.items) },
		func() interface{} { b := it.items; it.items = nil; return b })

	return it
}

// Object metadata methods.

func (c *httpStorageClient) DeleteObject(ctx context.Context, bucket, object string, gen int64, conds *Conditions, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)
	req := c.raw.Objects.Delete(bucket, object).Context(ctx)
	if err := applyConds("Delete", gen, conds, req); err != nil {
		return err
	}
	if s.userProject != "" {
		req.UserProject(s.userProject)
	}
	err := run(ctx, func(ctx context.Context) error { return req.Context(ctx).Do() }, s.retry, s.idempotent, withOperation("DeleteObject"), withBucket(bucket), withObject(object))
	return formatObjectErr(err)
}

func (c *httpStorageClient) GetObject(ctx context.Context, params *getObjectParams, opts ...storageOption) (*ObjectAttrs, error) {
	s := callSettings(c.settings, opts...)
	req := c.raw.Objects.Get(params.bucket, params.object).Projection("full").Context(ctx)
	if err := applyConds("Attrs", params.gen, params.conds, req); err != nil {
		return nil, err
	}
	if s.userProject != "" {
		req.UserProject(s.userProject)
	}
	if err := setEncryptionHeaders(req.Header(), params.encryptionKey, false); err != nil {
		return nil, err
	}
	if params.softDeleted {
		req.SoftDeleted(params.softDeleted)
	}

	var obj *raw.Object
	var err error
	err = run(ctx, func(ctx context.Context) error {
		obj, err = req.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent, withOperation("GetObject"), withBucket(params.bucket), withObject(params.object))
	if err != nil {
		return nil, formatObjectErr(err)
	}
	return newObject(obj), nil
}

func (c *httpStorageClient) UpdateObject(ctx context.Context, params *updateObjectParams, opts ...storageOption) (*ObjectAttrs, error) {
	uattrs := params.uattrs
	s := callSettings(c.settings, opts...)

	var attrs ObjectAttrs
	// Lists of fields to send, and set to null, in the JSON.
	var forceSendFields, nullFields []string
	if uattrs.ContentType != nil {
		attrs.ContentType = optional.ToString(uattrs.ContentType)
		// For ContentType, sending the empty string is a no-op.
		// Instead we send a null.
		if attrs.ContentType == "" {
			nullFields = append(nullFields, "ContentType")
		} else {
			forceSendFields = append(forceSendFields, "ContentType")
		}
	}
	if uattrs.ContentLanguage != nil {
		attrs.ContentLanguage = optional.ToString(uattrs.ContentLanguage)
		// For ContentLanguage it's an error to send the empty string.
		// Instead we send a null.
		if attrs.ContentLanguage == "" {
			nullFields = append(nullFields, "ContentLanguage")
		} else {
			forceSendFields = append(forceSendFields, "ContentLanguage")
		}
	}
	if uattrs.ContentEncoding != nil {
		attrs.ContentEncoding = optional.ToString(uattrs.ContentEncoding)
		forceSendFields = append(forceSendFields, "ContentEncoding")
	}
	if uattrs.ContentDisposition != nil {
		attrs.ContentDisposition = optional.ToString(uattrs.ContentDisposition)
		forceSendFields = append(forceSendFields, "ContentDisposition")
	}
	if uattrs.CacheControl != nil {
		attrs.CacheControl = optional.ToString(uattrs.CacheControl)
		forceSendFields = append(forceSendFields, "CacheControl")
	}
	if uattrs.EventBasedHold != nil {
		attrs.EventBasedHold = optional.ToBool(uattrs.EventBasedHold)
		forceSendFields = append(forceSendFields, "EventBasedHold")
	}
	if uattrs.TemporaryHold != nil {
		attrs.TemporaryHold = optional.ToBool(uattrs.TemporaryHold)
		forceSendFields = append(forceSendFields, "TemporaryHold")
	}
	if !uattrs.CustomTime.IsZero() {
		attrs.CustomTime = uattrs.CustomTime
		forceSendFields = append(forceSendFields, "CustomTime")
	}
	if uattrs.Metadata != nil {
		attrs.Metadata = uattrs.Metadata
		if len(attrs.Metadata) == 0 {
			// Sending the empty map is a no-op. We send null instead.
			nullFields = append(nullFields, "Metadata")
		} else {
			forceSendFields = append(forceSendFields, "Metadata")
		}
	}
	if uattrs.ACL != nil {
		attrs.ACL = uattrs.ACL
		// It's an error to attempt to delete the ACL, so
		// we don't append to nullFields here.
		forceSendFields = append(forceSendFields, "Acl")
	}
	if uattrs.Retention != nil {
		// For ObjectRetention it's an error to send empty fields.
		// Instead we send a null as the user's intention is to remove.
		if uattrs.Retention.Mode == "" && uattrs.Retention.RetainUntil.IsZero() {
			nullFields = append(nullFields, "Retention")
		} else {
			attrs.Retention = uattrs.Retention
			forceSendFields = append(forceSendFields, "Retention")
		}
	}

	if uattrs.Contexts != nil && uattrs.Contexts.Custom != nil {
		if len(uattrs.Contexts.Custom) == 0 {
			// To delete all contexts, "Contexts" must be added to nullFields.
			// Sending empty Custom map in the request body is a no-op without this.
			nullFields = append(nullFields, "Contexts")
		} else {
			attrs.Contexts = uattrs.Contexts
			// This is to ensure any new values or deletions are updated
			forceSendFields = append(forceSendFields, "Contexts")
		}
	}

	rawObj := attrs.toRawObject(params.bucket)
	rawObj.ForceSendFields = forceSendFields
	rawObj.NullFields = nullFields
	call := c.raw.Objects.Patch(params.bucket, params.object, rawObj).Projection("full")
	if err := applyConds("Update", params.gen, params.conds, call); err != nil {
		return nil, err
	}
	if s.userProject != "" {
		call.UserProject(s.userProject)
	}
	if uattrs.PredefinedACL != "" {
		call.PredefinedAcl(uattrs.PredefinedACL)
	}
	if err := setEncryptionHeaders(call.Header(), params.encryptionKey, false); err != nil {
		return nil, err
	}

	if params.overrideRetention != nil {
		call.OverrideUnlockedRetention(*params.overrideRetention)
	}

	var obj *raw.Object
	var err error
	err = run(ctx, func(ctx context.Context) error { obj, err = call.Context(ctx).Do(); return err }, s.retry, s.idempotent, withOperation("UpdateObject"), withBucket(params.bucket), withObject(params.object))
	if err != nil {
		return nil, formatObjectErr(err)
	}
	return newObject(obj), nil
}

func (c *httpStorageClient) RestoreObject(ctx context.Context, params *restoreObjectParams, opts ...storageOption) (*ObjectAttrs, error) {
	s := callSettings(c.settings, opts...)
	req := c.raw.Objects.Restore(params.bucket, params.object, params.gen).Context(ctx)
	// Do not set the generation here since it's not an optional condition; it gets set above.
	if err := applyConds("RestoreObject", defaultGen, params.conds, req); err != nil {
		return nil, err
	}
	if s.userProject != "" {
		req.UserProject(s.userProject)
	}
	if params.copySourceACL {
		req.CopySourceAcl(params.copySourceACL)
	}
	if err := setEncryptionHeaders(req.Header(), params.encryptionKey, false); err != nil {
		return nil, err
	}

	var obj *raw.Object
	var err error
	err = run(ctx, func(ctx context.Context) error { obj, err = req.Context(ctx).Do(); return err }, s.retry, s.idempotent, withOperation("RestoreObject"), withBucket(params.bucket), withObject(params.object))
	if err != nil {
		return nil, formatObjectErr(err)
	}
	return newObject(obj), err
}

func (c *httpStorageClient) MoveObject(ctx context.Context, params *moveObjectParams, opts ...storageOption) (*ObjectAttrs, error) {
	s := callSettings(c.settings, opts...)
	req := c.raw.Objects.Move(params.bucket, params.srcObject, params.dstObject).Context(ctx)
	if err := applyConds("MoveObjectDestination", defaultGen, params.dstConds, req); err != nil {
		return nil, err
	}
	if err := applySourceConds("MoveObjectSource", defaultGen, params.srcConds, req); err != nil {
		return nil, err
	}
	if s.userProject != "" {
		req.UserProject(s.userProject)
	}
	if err := setEncryptionHeaders(req.Header(), params.encryptionKey, false); err != nil {
		return nil, err
	}
	var obj *raw.Object
	var err error
	err = run(ctx, func(ctx context.Context) error { obj, err = req.Context(ctx).Do(); return err }, s.retry, s.idempotent, withOperation("MoveObject"), withBucket(params.bucket), withObject(params.srcObject))
	if err != nil {
		return nil, formatObjectErr(err)
	}
	return newObject(obj), err
}

// Default Object ACL methods.

func (c *httpStorageClient) DeleteDefaultObjectACL(ctx context.Context, bucket string, entity ACLEntity, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)
	req := c.raw.DefaultObjectAccessControls.Delete(bucket, string(entity))
	configureACLCall(ctx, s.userProject, req)
	return run(ctx, func(ctx context.Context) error { return req.Context(ctx).Do() }, s.retry, s.idempotent)
}

func (c *httpStorageClient) ListDefaultObjectACLs(ctx context.Context, bucket string, opts ...storageOption) ([]ACLRule, error) {
	s := callSettings(c.settings, opts...)
	var acls *raw.ObjectAccessControls
	var err error
	req := c.raw.DefaultObjectAccessControls.List(bucket)
	configureACLCall(ctx, s.userProject, req)
	err = run(ctx, func(ctx context.Context) error {
		acls, err = req.Context(ctx).Do()
		return err
	}, s.retry, true)
	if err != nil {
		return nil, err
	}
	return toObjectACLRules(acls.Items), nil
}
func (c *httpStorageClient) UpdateDefaultObjectACL(ctx context.Context, bucket string, entity ACLEntity, role ACLRole, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)
	type setRequest interface {
		Do(opts ...googleapi.CallOption) (*raw.ObjectAccessControl, error)
		Header() http.Header
	}
	acl := &raw.ObjectAccessControl{
		Bucket: bucket,
		Entity: string(entity),
		Role:   string(role),
	}
	var err error
	req := c.raw.DefaultObjectAccessControls.Update(bucket, string(entity), acl)
	configureACLCall(ctx, s.userProject, req)
	return run(ctx, func(ctx context.Context) error {
		_, err = req.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
}

// Bucket ACL methods.

func (c *httpStorageClient) DeleteBucketACL(ctx context.Context, bucket string, entity ACLEntity, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)
	req := c.raw.BucketAccessControls.Delete(bucket, string(entity))
	configureACLCall(ctx, s.userProject, req)
	return run(ctx, func(ctx context.Context) error { return req.Context(ctx).Do() }, s.retry, s.idempotent)
}

func (c *httpStorageClient) ListBucketACLs(ctx context.Context, bucket string, opts ...storageOption) ([]ACLRule, error) {
	s := callSettings(c.settings, opts...)
	var acls *raw.BucketAccessControls
	var err error
	req := c.raw.BucketAccessControls.List(bucket)
	configureACLCall(ctx, s.userProject, req)
	err = run(ctx, func(ctx context.Context) error {
		acls, err = req.Context(ctx).Do()
		return err
	}, s.retry, true)
	if err != nil {
		return nil, err
	}
	return toBucketACLRules(acls.Items), nil
}

func (c *httpStorageClient) UpdateBucketACL(ctx context.Context, bucket string, entity ACLEntity, role ACLRole, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)
	acl := &raw.BucketAccessControl{
		Bucket: bucket,
		Entity: string(entity),
		Role:   string(role),
	}
	req := c.raw.BucketAccessControls.Update(bucket, string(entity), acl)
	configureACLCall(ctx, s.userProject, req)
	var err error
	return run(ctx, func(ctx context.Context) error {
		_, err = req.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
}

// configureACLCall sets the context and user project on the apiary library call.
// This will panic if the call does not have the correct methods.
func configureACLCall(ctx context.Context, userProject string, call interface{ Header() http.Header }) {
	vc := reflect.ValueOf(call)
	vc.MethodByName("Context").Call([]reflect.Value{reflect.ValueOf(ctx)})
	if userProject != "" {
		vc.MethodByName("UserProject").Call([]reflect.Value{reflect.ValueOf(userProject)})
	}
}

// Object ACL methods.

func (c *httpStorageClient) DeleteObjectACL(ctx context.Context, bucket, object string, entity ACLEntity, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)
	req := c.raw.ObjectAccessControls.Delete(bucket, object, string(entity))
	configureACLCall(ctx, s.userProject, req)
	return run(ctx, func(ctx context.Context) error { return req.Context(ctx).Do() }, s.retry, s.idempotent)
}

// ListObjectACLs retrieves object ACL entries. By default, it operates on the latest generation of this object.
// Selecting a specific generation of this object is not currently supported by the client.
func (c *httpStorageClient) ListObjectACLs(ctx context.Context, bucket, object string, opts ...storageOption) ([]ACLRule, error) {
	s := callSettings(c.settings, opts...)
	var acls *raw.ObjectAccessControls
	var err error
	req := c.raw.ObjectAccessControls.List(bucket, object)
	configureACLCall(ctx, s.userProject, req)
	err = run(ctx, func(ctx context.Context) error {
		acls, err = req.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
	if err != nil {
		return nil, err
	}
	return toObjectACLRules(acls.Items), nil
}

func (c *httpStorageClient) UpdateObjectACL(ctx context.Context, bucket, object string, entity ACLEntity, role ACLRole, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)
	type setRequest interface {
		Do(opts ...googleapi.CallOption) (*raw.ObjectAccessControl, error)
		Header() http.Header
	}

	acl := &raw.ObjectAccessControl{
		Bucket: bucket,
		Entity: string(entity),
		Role:   string(role),
	}
	var err error
	req := c.raw.ObjectAccessControls.Update(bucket, object, string(entity), acl)
	configureACLCall(ctx, s.userProject, req)
	return run(ctx, func(ctx context.Context) error {
		_, err = req.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
}

// Media operations.

func (c *httpStorageClient) ComposeObject(ctx context.Context, req *composeObjectRequest, opts ...storageOption) (*ObjectAttrs, error) {
	s := callSettings(c.settings, opts...)
	rawReq := &raw.ComposeRequest{}
	// Compose requires a non-empty Destination, so we always set it,
	// even if the caller-provided ObjectAttrs is the zero value.
	rawReq.Destination = req.dstObject.attrs.toRawObject(req.dstBucket)
	if req.sendCRC32C {
		rawReq.Destination.Crc32c = encodeUint32(req.dstObject.attrs.CRC32C)
	}
	for _, src := range req.srcs {
		srcObj := &raw.ComposeRequestSourceObjects{
			Name: src.name,
		}
		if err := applyConds("ComposeFrom source", src.gen, src.conds, composeSourceObj{srcObj}); err != nil {
			return nil, err
		}
		rawReq.SourceObjects = append(rawReq.SourceObjects, srcObj)
	}

	call := c.raw.Objects.Compose(req.dstBucket, req.dstObject.name, rawReq)
	if err := applyConds("ComposeFrom destination", defaultGen, req.dstObject.conds, call); err != nil {
		return nil, err
	}
	if s.userProject != "" {
		call.UserProject(s.userProject)
	}
	if req.predefinedACL != "" {
		call.DestinationPredefinedAcl(req.predefinedACL)
	}
	if err := setEncryptionHeaders(call.Header(), req.dstObject.encryptionKey, false); err != nil {
		return nil, err
	}
	var obj *raw.Object

	var err error
	retryCall := func(ctx context.Context) error { obj, err = call.Context(ctx).Do(); return err }

	if err := run(ctx, retryCall, s.retry, s.idempotent, withOperation("ComposeObject"), withBucket(req.dstBucket), withObject(req.dstObject.name)); err != nil {
		return nil, formatObjectErr(err)
	}
	return newObject(obj), nil
}
func (c *httpStorageClient) RewriteObject(ctx context.Context, req *rewriteObjectRequest, opts ...storageOption) (*rewriteObjectResponse, error) {
	s := callSettings(c.settings, opts...)
	rawObject := req.dstObject.attrs.toRawObject("")
	call := c.raw.Objects.Rewrite(req.srcObject.bucket, req.srcObject.name, req.dstObject.bucket, req.dstObject.name, rawObject)

	call.Projection("full")
	if req.token != "" {
		call.RewriteToken(req.token)
	}
	if req.dstObject.keyName != "" {
		call.DestinationKmsKeyName(req.dstObject.keyName)
	}
	if req.predefinedACL != "" {
		call.DestinationPredefinedAcl(req.predefinedACL)
	}
	if err := applyConds("Copy destination", defaultGen, req.dstObject.conds, call); err != nil {
		return nil, err
	}
	if err := applySourceConds("Copy source", req.srcObject.gen, req.srcObject.conds, call); err != nil {
		return nil, err
	}
	if s.userProject != "" {
		call.UserProject(s.userProject)
	}
	// Set destination encryption headers.
	if err := setEncryptionHeaders(call.Header(), req.dstObject.encryptionKey, false); err != nil {
		return nil, err
	}
	// Set source encryption headers.
	if err := setEncryptionHeaders(call.Header(), req.srcObject.encryptionKey, true); err != nil {
		return nil, err
	}

	if req.maxBytesRewrittenPerCall != 0 {
		call.MaxBytesRewrittenPerCall(req.maxBytesRewrittenPerCall)
	}

	var res *raw.RewriteResponse
	var err error

	retryCall := func(ctx context.Context) error { res, err = call.Context(ctx).Do(); return err }

	if err := run(ctx, retryCall, s.retry, s.idempotent, withOperation("RewriteObject"), withBucket(req.srcObject.bucket), withObject(req.srcObject.name)); err != nil {
		return nil, formatObjectErr(err)
	}

	r := &rewriteObjectResponse{
		done:     res.Done,
		written:  res.TotalBytesRewritten,
		size:     res.ObjectSize,
		token:    res.RewriteToken,
		resource: newObject(res.Resource),
	}

	return r, nil
}

// NewMultiRangeDownloader is not supported by http client.
func (c *httpStorageClient) NewMultiRangeDownloader(ctx context.Context, params *newMultiRangeDownloaderParams, opts ...storageOption) (mr *MultiRangeDownloader, err error) {
	return nil, errMethodNotSupported
}

func (c *httpStorageClient) NewRangeReader(ctx context.Context, params *newRangeReaderParams, opts ...storageOption) (r *Reader, err error) {
	ctx, _ = startSpan(ctx, "httpStorageClient.NewRangeReader")
	defer func() { endSpan(ctx, err) }()

	s := callSettings(c.settings, opts...)

	if c.config.useJSONforReads {
		return c.newRangeReaderJSON(ctx, params, s)
	}
	return c.newRangeReaderXML(ctx, params, s)
}

func (c *httpStorageClient) newRangeReaderXML(ctx context.Context, params *newRangeReaderParams, s *settings) (r *Reader, err error) {
	requestID := uuid.New()
	u := &url.URL{
		Scheme:  c.scheme,
		Host:    c.xmlHost,
		Path:    fmt.Sprintf("/%s/%s", params.bucket, params.object),
		RawPath: fmt.Sprintf("/%s/%s", params.bucket, url.PathEscape(params.object)),
	}
	verb := "GET"
	if params.length == 0 {
		verb = "HEAD"
	}
	req, err := http.NewRequest(verb, u.String(), nil)
	if err != nil {
		return nil, err
	}

	if s.userProject != "" {
		req.Header.Set("X-Goog-User-Project", s.userProject)
	}

	if err := setRangeReaderHeaders(req.Header, params); err != nil {
		return nil, err
	}

	reopen := readerReopen(ctx, req.Header, params, s,
		func(ctx context.Context) (*http.Response, error) {
			setHeadersFromCtx(ctx, req.Header)

			if c.dynamicReadReqStallTimeout == nil {
				return c.hc.Do(req.WithContext(ctx))
			}

			cancelCtx, cancel := context.WithCancel(ctx)
			var (
				res *http.Response
				err error
			)

			done := make(chan bool)
			go func() {
				reqStartTime := time.Now()
				res, err = c.hc.Do(req.WithContext(cancelCtx))
				if err == nil {
					reqLatency := time.Since(reqStartTime)
					c.dynamicReadReqStallTimeout.update(params.bucket, reqLatency)
				} else if errors.Is(err, context.Canceled) {
					// context.Canceled means operation took more than current dynamicTimeout,
					// hence should be increased.
					c.dynamicReadReqStallTimeout.increase(params.bucket)
				}
				done <- true
			}()

			// Wait until stall timeout or request is successful.
			stallTimeout := c.dynamicReadReqStallTimeout.getValue(params.bucket)
			timer := time.After(stallTimeout)
			select {
			case <-timer:
				log.Printf("[%s] stalled read-req cancelled after %fs", requestID, stallTimeout.Seconds())
				cancel()
				<-done
				if res != nil && res.Body != nil {
					res.Body.Close()
				}
				return res, context.DeadlineExceeded
			case <-done:
				cancel = nil
			}
			return res, err
		},
		func() error { return setConditionsHeaders(req.Header, params.conds) },
		func() { req.URL.RawQuery = fmt.Sprintf("generation=%d", params.gen) })

	res, err := reopen(0)
	if err != nil {
		return nil, err
	}
	return parseReadResponse(res, params, reopen)
}

func (c *httpStorageClient) newRangeReaderJSON(ctx context.Context, params *newRangeReaderParams, s *settings) (r *Reader, err error) {
	call := c.raw.Objects.Get(params.bucket, params.object)

	call.Projection("full")

	if s.userProject != "" {
		call.UserProject(s.userProject)
	}

	if err := setRangeReaderHeaders(call.Header(), params); err != nil {
		return nil, err
	}

	reopen := readerReopen(ctx, call.Header(), params, s, func(ctx context.Context) (*http.Response, error) { return call.Context(ctx).Download() },
		func() error { return applyConds("NewReader", params.gen, params.conds, call) },
		func() { call.Generation(params.gen) })

	res, err := reopen(0)
	if err != nil {
		return nil, err
	}
	return parseReadResponse(res, params, reopen)
}

// httpInternalWriter writes data for an HTTP upload. For single-shot uploads,
// it also calculates the CRC32C checksum of the data and validates it against
// the checksum returned by the server.
type httpInternalWriter struct {
	*io.PipeWriter
	chunkSize          int
	checksumDisabled   bool
	fullObjectChecksum uint32
	// In single-shot mode, the server-provided checksum is received on this
	// channel for validation after the upload is complete.
	serverChecksumChan chan uint32
}

// validateChecksum validates the computed checksum against the server-provided checksum.
func (hiw *httpInternalWriter) validateChecksumFromServer() error {
	serverChecksum, ok := <-hiw.serverChecksumChan
	// Do not check for channel closure as error is already set on the writer
	// if serverChecksumChan is closed without checksum
	if ok && hiw.fullObjectChecksum != serverChecksum {
		return fmt.Errorf("storage: object checksum mismatch: computed %q, server %q; the bucket may contain corrupted object", encodeUint32(hiw.fullObjectChecksum), encodeUint32(serverChecksum))
	}
	return nil
}

func (hiw *httpInternalWriter) Write(data []byte) (n int, err error) {
	if !hiw.checksumDisabled && hiw.chunkSize == 0 {
		hiw.fullObjectChecksum = crc32.Update(hiw.fullObjectChecksum, crc32cTable, data)
	}
	return hiw.PipeWriter.Write(data)
}

func (hiw *httpInternalWriter) Close() error {
	if err := hiw.PipeWriter.Close(); err != nil {
		return err
	}
	if !hiw.checksumDisabled && hiw.chunkSize == 0 {
		return hiw.validateChecksumFromServer()
	}
	return nil
}

func (hiw *httpInternalWriter) Flush() (int64, error) {
	return 0, errors.New("Writer.Flush is only supported for gRPC-based clients")
}

func (c *httpStorageClient) OpenWriter(params *openWriterParams, opts ...storageOption) (internalWriter, error) {
	if params.append {
		return nil, errors.New("storage: append not supported on HTTP Client; use gRPC")
	}

	s := callSettings(c.settings, opts...)
	errorf := params.setError
	setObj := params.setObj
	progress := params.progress
	attrs := params.attrs

	mediaOpts := []googleapi.MediaOption{
		googleapi.ChunkSize(params.chunkSize),
	}
	if c := attrs.ContentType; c != "" || params.forceEmptyContentType {
		mediaOpts = append(mediaOpts, googleapi.ContentType(c))
	}
	if params.chunkRetryDeadline != 0 {
		mediaOpts = append(mediaOpts, googleapi.ChunkRetryDeadline(params.chunkRetryDeadline))
	}
	if params.chunkTransferTimeout != 0 {
		mediaOpts = append(mediaOpts, googleapi.ChunkTransferTimeout(params.chunkTransferTimeout))
	}

	pr, pw := io.Pipe()
	var (
		serverChecksumChan = make(chan uint32, 1)
		checksumDisabled   = params.disableAutoChecksum || params.sendCRC32C
	)
	if !checksumDisabled {
		mediaOpts = append(mediaOpts, googleapi.EnableAutoChecksum())
	}
	go func() {
		defer func() {
			close(params.donec)
			close(serverChecksumChan)
		}()
		rawObj := attrs.toRawObject(params.bucket)
		if params.sendCRC32C {
			rawObj.Crc32c = encodeUint32(attrs.CRC32C)
		}
		if attrs.MD5 != nil {
			rawObj.Md5Hash = base64.StdEncoding.EncodeToString(attrs.MD5)
		}
		call := c.raw.Objects.Insert(params.bucket, rawObj).
			Media(pr, mediaOpts...).
			Projection("full").
			Context(params.ctx).
			Name(params.attrs.Name)
		call.ProgressUpdater(func(n, _ int64) { progress(n) })

		if attrs.KMSKeyName != "" {
			call.KmsKeyName(attrs.KMSKeyName)
		}
		if attrs.PredefinedACL != "" {
			call.PredefinedAcl(attrs.PredefinedACL)
		}
		if err := setEncryptionHeaders(call.Header(), params.encryptionKey, false); err != nil {
			errorf(err)
			pr.CloseWithError(err)
			return
		}
		var resp *raw.Object
		err := applyConds("NewWriter", defaultGen, params.conds, call)
		if err == nil {
			if s.userProject != "" {
				call.UserProject(s.userProject)
			}
			// TODO(tritone): Remove this code when Uploads begin to support
			// retry attempt header injection with "client header" injection.
			setClientHeader(call.Header())

			// The internals that perform call.Do automatically retry both the initial
			// call to set up the upload as well as calls to upload individual chunks
			// for a resumable upload (as long as the chunk size is non-zero). Hence
			// there is no need to add retries here.

			// Retry only when the operation is idempotent or the retry policy is RetryAlways.
			var useRetry bool
			if (s.retry == nil || s.retry.policy == RetryIdempotent) && s.idempotent {
				useRetry = true
			} else if s.retry != nil && s.retry.policy == RetryAlways {
				useRetry = true
			}
			if useRetry {
				if s.retry != nil {
					// Wrap shouldRetry to adapt to the googleapi WithRetry signature.
					var retryFunc func(error) bool
					if s.retry.shouldRetry != nil {
						retryFunc = func(err error) bool {
							return s.retry.shouldRetry(err, nil)
						}
					}
					call.WithRetry(s.retry.backoff, retryFunc)
				} else {
					call.WithRetry(nil, nil)
				}
			}
			resp, err = call.Do()
		}
		if err != nil {
			errorf(err)
			pr.CloseWithError(err)
			return
		}
		newObj := newObject(resp)
		if !checksumDisabled && params.chunkSize == 0 {
			serverChecksumChan <- newObj.CRC32C
		}
		setObj(newObj)
	}()
	return &httpInternalWriter{
		PipeWriter:         pw,
		chunkSize:          params.chunkSize,
		serverChecksumChan: serverChecksumChan,
		checksumDisabled:   checksumDisabled,
	}, nil
}

// IAM methods.

func (c *httpStorageClient) GetIamPolicy(ctx context.Context, resource string, version int32, opts ...storageOption) (*iampb.Policy, error) {
	s := callSettings(c.settings, opts...)
	call := c.raw.Buckets.GetIamPolicy(resource).OptionsRequestedPolicyVersion(int64(version))
	if s.userProject != "" {
		call.UserProject(s.userProject)
	}
	var rp *raw.Policy
	err := run(ctx, func(ctx context.Context) error {
		var err error
		rp, err = call.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
	if err != nil {
		return nil, err
	}
	return iamFromStoragePolicy(rp), nil
}

func (c *httpStorageClient) SetIamPolicy(ctx context.Context, resource string, policy *iampb.Policy, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)

	rp := iamToStoragePolicy(policy)
	call := c.raw.Buckets.SetIamPolicy(resource, rp)
	if s.userProject != "" {
		call.UserProject(s.userProject)
	}

	return run(ctx, func(ctx context.Context) error {
		_, err := call.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
}

func (c *httpStorageClient) TestIamPermissions(ctx context.Context, resource string, permissions []string, opts ...storageOption) ([]string, error) {
	s := callSettings(c.settings, opts...)
	call := c.raw.Buckets.TestIamPermissions(resource, permissions)
	if s.userProject != "" {
		call.UserProject(s.userProject)
	}
	var res *raw.TestIamPermissionsResponse
	err := run(ctx, func(ctx context.Context) error {
		var err error
		res, err = call.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
	if err != nil {
		return nil, err
	}
	return res.Permissions, nil
}

// HMAC Key methods.

func (c *httpStorageClient) GetHMACKey(ctx context.Context, project, accessID string, opts ...storageOption) (*HMACKey, error) {
	s := callSettings(c.settings, opts...)
	call := c.raw.Projects.HmacKeys.Get(project, accessID)
	if s.userProject != "" {
		call = call.UserProject(s.userProject)
	}

	var metadata *raw.HmacKeyMetadata
	var err error
	if err := run(ctx, func(ctx context.Context) error {
		metadata, err = call.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent); err != nil {
		return nil, err
	}
	hk := &raw.HmacKey{
		Metadata: metadata,
	}
	return toHMACKeyFromRaw(hk, false)
}

func (c *httpStorageClient) ListHMACKeys(ctx context.Context, project, serviceAccountEmail string, showDeletedKeys bool, opts ...storageOption) *HMACKeysIterator {
	s := callSettings(c.settings, opts...)
	it := &HMACKeysIterator{
		ctx:       ctx,
		raw:       c.raw.Projects.HmacKeys,
		projectID: project,
		retry:     s.retry,
	}
	fetch := func(pageSize int, pageToken string) (token string, err error) {
		call := c.raw.Projects.HmacKeys.List(project)
		if pageToken != "" {
			call = call.PageToken(pageToken)
		}
		if pageSize > 0 {
			call = call.MaxResults(int64(pageSize))
		}
		if showDeletedKeys {
			call = call.ShowDeletedKeys(true)
		}
		if s.userProject != "" {
			call = call.UserProject(s.userProject)
		}
		if serviceAccountEmail != "" {
			call = call.ServiceAccountEmail(serviceAccountEmail)
		}

		var resp *raw.HmacKeysMetadata
		err = run(it.ctx, func(ctx context.Context) error {
			resp, err = call.Context(ctx).Do()
			return err
		}, s.retry, s.idempotent)
		if err != nil {
			return "", err
		}

		for _, metadata := range resp.Items {
			hk := &raw.HmacKey{
				Metadata: metadata,
			}
			hkey, err := toHMACKeyFromRaw(hk, true)
			if err != nil {
				return "", err
			}
			it.hmacKeys = append(it.hmacKeys, hkey)
		}
		return resp.NextPageToken, nil
	}

	it.pageInfo, it.nextFunc = iterator.NewPageInfo(
		fetch,
		func() int { return len(it.hmacKeys) - it.index },
		func() interface{} {
			prev := it.hmacKeys
			it.hmacKeys = it.hmacKeys[:0]
			it.index = 0
			return prev
		})
	return it
}

func (c *httpStorageClient) UpdateHMACKey(ctx context.Context, project, serviceAccountEmail, accessID string, attrs *HMACKeyAttrsToUpdate, opts ...storageOption) (*HMACKey, error) {
	s := callSettings(c.settings, opts...)
	call := c.raw.Projects.HmacKeys.Update(project, accessID, &raw.HmacKeyMetadata{
		Etag:  attrs.Etag,
		State: string(attrs.State),
	})
	if s.userProject != "" {
		call = call.UserProject(s.userProject)
	}

	var metadata *raw.HmacKeyMetadata
	var err error
	if err := run(ctx, func(ctx context.Context) error {
		metadata, err = call.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent); err != nil {
		return nil, err
	}
	hk := &raw.HmacKey{
		Metadata: metadata,
	}
	return toHMACKeyFromRaw(hk, false)
}

func (c *httpStorageClient) CreateHMACKey(ctx context.Context, project, serviceAccountEmail string, opts ...storageOption) (*HMACKey, error) {
	s := callSettings(c.settings, opts...)
	call := c.raw.Projects.HmacKeys.Create(project, serviceAccountEmail)
	if s.userProject != "" {
		call = call.UserProject(s.userProject)
	}

	var hk *raw.HmacKey
	if err := run(ctx, func(ctx context.Context) error {
		h, err := call.Context(ctx).Do()
		hk = h
		return err
	}, s.retry, s.idempotent); err != nil {
		return nil, err
	}
	return toHMACKeyFromRaw(hk, true)
}

func (c *httpStorageClient) DeleteHMACKey(ctx context.Context, project string, accessID string, opts ...storageOption) error {
	s := callSettings(c.settings, opts...)
	call := c.raw.Projects.HmacKeys.Delete(project, accessID)
	if s.userProject != "" {
		call = call.UserProject(s.userProject)
	}
	return run(ctx, func(ctx context.Context) error {
		return call.Context(ctx).Do()
	}, s.retry, s.idempotent)
}

// Notification methods.

// ListNotifications returns all the Notifications configured for this bucket, as a map indexed by notification ID.
//
// Note: This API does not support pagination. However, entity limits cap the number of notifications on a single bucket,
// so all results will be returned in the first response. See https://cloud.google.com/storage/quotas#buckets.
func (c *httpStorageClient) ListNotifications(ctx context.Context, bucket string, opts ...storageOption) (n map[string]*Notification, err error) {
	s := callSettings(c.settings, opts...)
	call := c.raw.Notifications.List(bucket)
	if s.userProject != "" {
		call.UserProject(s.userProject)
	}
	var res *raw.Notifications
	err = run(ctx, func(ctx context.Context) error {
		res, err = call.Context(ctx).Do()
		return err
	}, s.retry, true)
	if err != nil {
		return nil, err
	}
	return notificationsToMap(res.Items), nil
}

func (c *httpStorageClient) CreateNotification(ctx context.Context, bucket string, n *Notification, opts ...storageOption) (ret *Notification, err error) {
	s := callSettings(c.settings, opts...)
	call := c.raw.Notifications.Insert(bucket, toRawNotification(n))
	if s.userProject != "" {
		call.UserProject(s.userProject)
	}
	var rn *raw.Notification
	err = run(ctx, func(ctx context.Context) error {
		rn, err = call.Context(ctx).Do()
		return err
	}, s.retry, s.idempotent)
	if err != nil {
		return nil, err
	}
	return toNotification(rn), nil
}

func (c *httpStorageClient) DeleteNotification(ctx context.Context, bucket string, id string, opts ...storageOption) (err error) {
	s := callSettings(c.settings, opts...)
	call := c.raw.Notifications.Delete(bucket, id)
	if s.userProject != "" {
		call.UserProject(s.userProject)
	}
	return run(ctx, func(ctx context.Context) error {
		return call.Context(ctx).Do()
	}, s.retry, s.idempotent)
}

type httpReader struct {
	body     io.ReadCloser
	seen     int64
	reopen   func(seen int64) (*http.Response, error)
	checkCRC bool   // should we check the CRC?
	wantCRC  uint32 // the CRC32c value the server sent in the header
	gotCRC   uint32 // running crc
}

func (r *httpReader) Read(p []byte) (int, error) {
	n := 0
	for len(p[n:]) > 0 {
		m, err := r.body.Read(p[n:])
		n += m
		r.seen += int64(m)
		if r.checkCRC {
			r.gotCRC = crc32.Update(r.gotCRC, crc32cTable, p[:n])
		}
		if err == nil {
			return n, nil
		}
		if err == io.EOF {
			// Check CRC here. It would be natural to check it in Close, but
			// everybody defers Close on the assumption that it doesn't return
			// anything worth looking at.
			if r.checkCRC {
				if r.gotCRC != r.wantCRC {
					return n, fmt.Errorf("storage: bad CRC on read: got %d, want %d",
						r.gotCRC, r.wantCRC)
				}
			}
			return n, err
		}
		// Read failed (likely due to connection issues), but we will try to reopen
		// the pipe and continue. Send a ranged read request that takes into account
		// the number of bytes we've already seen.

		// Close the current body before retrying. Otherwise we leave a
		// connection open and the retry might fail due to quota issues.
		r.body.Close()

		res, err := r.reopen(r.seen)
		if err != nil {
			// reopen already retries
			return n, err
		}
		r.body = res.Body
	}
	return n, nil
}

func (r *httpReader) Close() error {
	return r.body.Close()
}

func setRangeReaderHeaders(h http.Header, params *newRangeReaderParams) error {
	if params.readCompressed {
		h.Set("Accept-Encoding", "gzip")
	}
	if err := setEncryptionHeaders(h, params.encryptionKey, false); err != nil {
		return err
	}
	return nil
}

// readerReopen initiates a Read with offset and length, assuming we
// have already read seen bytes.
func readerReopen(ctx context.Context, header http.Header, params *newRangeReaderParams, s *settings,
	doDownload func(context.Context) (*http.Response, error), applyConditions func() error, setGeneration func()) func(int64) (*http.Response, error) {
	return func(seen int64) (*http.Response, error) {
		// If the context has already expired, return immediately without making a
		// call.
		if err := ctx.Err(); err != nil {
			return nil, err
		}
		start := params.offset + seen
		if params.length < 0 && start < 0 {
			header.Set("Range", fmt.Sprintf("bytes=%d", start))
		} else if params.length < 0 && start > 0 {
			header.Set("Range", fmt.Sprintf("bytes=%d-", start))
		} else if params.length > 0 {
			// The end character isn't affected by how many bytes we've seen.
			header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, params.offset+params.length-1))
		}
		// We wait to assign conditions here because the generation number can change in between reopen() runs.
		if err := applyConditions(); err != nil {
			return nil, err
		}
		// If an object generation is specified, include generation as query string parameters.
		if params.gen >= 0 {
			setGeneration()
		}

		var err error
		var res *http.Response
		err = run(ctx, func(ctx context.Context) error {
			res, err = doDownload(ctx)
			if err != nil {
				return formatObjectErr(err)
			}

			if res.StatusCode == http.StatusNotFound {
				// this check is necessary only for XML
				res.Body.Close()
				return ErrObjectNotExist
			}
			if res.StatusCode < 200 || res.StatusCode > 299 {
				body, _ := io.ReadAll(res.Body)
				res.Body.Close()
				return &googleapi.Error{
					Code:   res.StatusCode,
					Header: res.Header,
					Body:   string(body),
				}
			}

			partialContentNotSatisfied :=
				!decompressiveTranscoding(res) &&
					start > 0 && params.length != 0 &&
					res.StatusCode != http.StatusPartialContent

			if partialContentNotSatisfied {
				res.Body.Close()
				return errors.New("storage: partial request not satisfied")
			}

			// With "Content-Encoding": "gzip" aka decompressive transcoding, GCS serves
			// back the whole file regardless of the range count passed in as per:
			//      https://cloud.google.com/storage/docs/transcoding#range,
			// thus we have to manually move the body forward by seen bytes.
			if decompressiveTranscoding(res) && seen > 0 {
				_, _ = io.CopyN(io.Discard, res.Body, seen)
			}

			// If a generation hasn't been specified, and this is the first response we get, let's record the
			// generation. In future requests we'll use this generation as a precondition to avoid data races.
			if params.gen < 0 && res.Header.Get("X-Goog-Generation") != "" {
				gen64, err := strconv.ParseInt(res.Header.Get("X-Goog-Generation"), 10, 64)
				if err != nil {
					res.Body.Close()
					return err
				}
				params.gen = gen64
			}
			return nil
		}, s.retry, s.idempotent)
		if err != nil {
			return nil, err
		}
		return res, nil
	}
}

func parseReadResponse(res *http.Response, params *newRangeReaderParams, reopen func(int64) (*http.Response, error)) (r *Reader, err error) {
	defer func() {
		if err != nil {
			res.Body.Close()
		}
	}()
	var (
		size        int64 // total size of object, even if a range was requested.
		checkCRC    bool
		crc         uint32
		startOffset int64 // non-zero if range request.
	)
	if res.StatusCode == http.StatusPartialContent {
		cr := strings.TrimSpace(res.Header.Get("Content-Range"))
		if !strings.HasPrefix(cr, "bytes ") || !strings.Contains(cr, "/") {
			err = fmt.Errorf("storage: invalid Content-Range %q", cr)
			return nil, err
		}
		// Content range is formatted <first byte>-<last byte>/<total size>. We take
		// the total size.
		size, err = strconv.ParseInt(cr[strings.LastIndex(cr, "/")+1:], 10, 64)
		if err != nil {
			err = fmt.Errorf("storage: invalid Content-Range %q: %w", cr, err)
			return nil, err
		}

		dashIndex := strings.Index(cr, "-")
		if dashIndex >= 0 {
			startOffset, err = strconv.ParseInt(cr[len("bytes="):dashIndex], 10, 64)
			if err != nil {
				err = fmt.Errorf("storage: invalid Content-Range %q: %w", cr, err)
				return nil, err
			}
		}
	} else {
		size = res.ContentLength
	}

	// Check the CRC iff all of the following hold:
	// - We asked for content (length != 0).
	// - We got all the content (status != PartialContent).
	// - The server sent a CRC header.
	// - The Go http stack did not uncompress the file.
	// - We were not served compressed data that was uncompressed on download.
	// The problem with the last two cases is that the CRC will not match -- GCS
	// computes it on the compressed contents, but we compute it on the
	// uncompressed contents.
	crc, checkCRC = parseCRC32c(res)
	if params.length == 0 || res.StatusCode == http.StatusPartialContent || res.Uncompressed || uncompressedByServer(res) {
		checkCRC = false
	}

	remain := res.ContentLength
	body := res.Body
	// If the user requested zero bytes, explicitly close and remove the request
	// body.
	if params.length == 0 {
		remain = 0
		body.Close()
		body = emptyBody
	}
	var metaGen int64
	if res.Header.Get("X-Goog-Metageneration") != "" {
		metaGen, err = strconv.ParseInt(res.Header.Get("X-Goog-Metageneration"), 10, 64)
		if err != nil {
			return nil, err
		}
	}

	var lm time.Time
	if res.Header.Get("Last-Modified") != "" {
		lm, err = http.ParseTime(res.Header.Get("Last-Modified"))
		if err != nil {
			return nil, err
		}
	}

	metadata := map[string]string{}
	for key, values := range res.Header {
		if len(values) > 0 && strings.HasPrefix(key, "X-Goog-Meta-") {
			key := key[len("X-Goog-Meta-"):]
			metadata[key] = values[0]
		}
	}

	attrs := ReaderObjectAttrs{
		Size:            size,
		ContentType:     res.Header.Get("Content-Type"),
		ContentEncoding: res.Header.Get("Content-Encoding"),
		CacheControl:    res.Header.Get("Cache-Control"),
		LastModified:    lm,
		StartOffset:     startOffset,
		Generation:      params.gen,
		Metageneration:  metaGen,
		CRC32C:          crc,
		Decompressed:    res.Uncompressed || uncompressedByServer(res),
	}
	return &Reader{
		Attrs:          attrs,
		objectMetadata: &metadata,
		size:           size,
		remain:         remain,
		checkCRC:       checkCRC,
		reader: &httpReader{
			reopen:   reopen,
			body:     body,
			wantCRC:  crc,
			checkCRC: checkCRC,
		},
	}, nil
}

// setHeadersFromCtx sets custom headers passed in via the context on the header,
// replacing any header with the same key (which avoids duplicating invocation headers).
// This is only required for XML; for gRPC & JSON requests this is handled in
// the GAPIC and Apiary layers respectively.
func setHeadersFromCtx(ctx context.Context, header http.Header) {
	ctxHeaders := callctx.HeadersFromContext(ctx)
	for k, vals := range ctxHeaders {
		// Merge x-goog-api-client values into a single space-separated value.
		if strings.EqualFold(k, xGoogHeaderKey) {
			alreadySetValues := header.Values(xGoogHeaderKey)
			vals = append(vals, alreadySetValues...)

			if len(vals) > 0 {
				xGoogHeader := vals[0]
				for _, v := range vals[1:] {
					xGoogHeader = strings.Join([]string{xGoogHeader, v}, " ")
				}
				header.Set(k, xGoogHeader)
			}
		} else {
			for _, v := range vals {
				header.Set(k, v)
			}
		}
	}
}
