package customizations

import (
	
	
	
	
	

	
	awsmiddleware 
	
	internalendpoints 
	
	
	smithyhttp 
)

// EndpointResolver interface for resolving service endpoints.
type EndpointResolver interface {
	ResolveEndpoint(region string, options EndpointResolverOptions) (aws.Endpoint, error)
}

// EndpointResolverOptions is the service endpoint resolver options
type EndpointResolverOptions = internalendpoints.Options

// UpdateEndpointParameterAccessor represents accessor functions used by the middleware
type UpdateEndpointParameterAccessor struct {
	// functional pointer to fetch bucket name from provided input.
	// The function is intended to take an input value, and
	// return a string pointer to value of string, and bool if
	// input has no bucket member.
	GetBucketFromInput func(interface{}) (*string, bool)
}

// UpdateEndpointOptions provides the options for the UpdateEndpoint middleware setup.
type UpdateEndpointOptions struct {
	// Accessor are parameter accessors used by the middleware
	Accessor UpdateEndpointParameterAccessor

	// use path style
	UsePathStyle bool

	// use transfer acceleration
	UseAccelerate bool

	// indicates if an operation supports s3 transfer acceleration.
	SupportsAccelerate bool

	// use ARN region
	UseARNRegion bool

	// Indicates that the operation should target the s3-object-lambda endpoint.
	// Used to direct operations that do not route based on an input ARN.
	TargetS3ObjectLambda bool

	// EndpointResolver used to resolve endpoints. This may be a custom endpoint resolver
	EndpointResolver EndpointResolver

	// EndpointResolverOptions used by endpoint resolver
	EndpointResolverOptions EndpointResolverOptions

	// DisableMultiRegionAccessPoints indicates multi-region access point support is disabled
	DisableMultiRegionAccessPoints bool
}

// UpdateEndpoint adds the middleware to the middleware stack based on the UpdateEndpointOptions.
func ( *middleware.Stack,  UpdateEndpointOptions) ( error) {
	const  = "OperationSerializer"

	// initial arn look up middleware
	 = .Initialize.Add(&s3shared.ARNLookup{
		GetARNValue: .Accessor.GetBucketFromInput,
	}, middleware.Before)
	if  != nil {
		return 
	}

	// process arn
	 = .Serialize.Insert(&processARNResource{
		UseARNRegion:                   .UseARNRegion,
		UseAccelerate:                  .UseAccelerate,
		EndpointResolver:               .EndpointResolver,
		EndpointResolverOptions:        .EndpointResolverOptions,
		DisableMultiRegionAccessPoints: .DisableMultiRegionAccessPoints,
	}, , middleware.Before)
	if  != nil {
		return 
	}

	// process whether the operation requires the s3-object-lambda endpoint
	// Occurs before operation serializer so that hostPrefix mutations
	// can be handled correctly.
	 = .Serialize.Insert(&s3ObjectLambdaEndpoint{
		UseEndpoint:             .TargetS3ObjectLambda,
		UseAccelerate:           .UseAccelerate,
		EndpointResolver:        .EndpointResolver,
		EndpointResolverOptions: .EndpointResolverOptions,
	}, , middleware.Before)
	if  != nil {
		return 
	}

	// remove bucket arn middleware
	 = .Serialize.Insert(&removeBucketFromPathMiddleware{}, , middleware.After)
	if  != nil {
		return 
	}

	// update endpoint to use options for path style and accelerate
	 = .Serialize.Insert(&updateEndpoint{
		usePathStyle:       .UsePathStyle,
		getBucketFromInput: .Accessor.GetBucketFromInput,
		useAccelerate:      .UseAccelerate,
		supportsAccelerate: .SupportsAccelerate,
	}, , middleware.After)
	if  != nil {
		return 
	}

	return 
}

type updateEndpoint struct {
	// path style options
	usePathStyle       bool
	getBucketFromInput func(interface{}) (*string, bool)

	// accelerate options
	useAccelerate      bool
	supportsAccelerate bool
}

// ID returns the middleware ID.
func (*updateEndpoint) () string {
	return "S3:UpdateEndpoint"
}

func ( *updateEndpoint) (
	 context.Context,  middleware.SerializeInput,  middleware.SerializeHandler,
) (
	 middleware.SerializeOutput,  middleware.Metadata,  error,
) {
	// if arn was processed, skip this middleware
	if ,  := s3shared.GetARNResourceFromContext();  {
		return .HandleSerialize(, )
	}

	// skip this customization if host name is set as immutable
	if smithyhttp.GetHostnameImmutable() {
		return .HandleSerialize(, )
	}

	,  := .Request.(*smithyhttp.Request)
	if ! {
		return , , fmt.Errorf("unknown request type %T", )
	}

	// check if accelerate is supported
	if .useAccelerate && !.supportsAccelerate {
		// accelerate is not supported, thus will be ignored
		log.Println("Transfer acceleration is not supported for the operation, ignoring UseAccelerate.")
		.useAccelerate = false
	}

	// transfer acceleration is not supported with path style urls
	if .useAccelerate && .usePathStyle {
		log.Println("UseAccelerate is not compatible with UsePathStyle, ignoring UsePathStyle.")
		.usePathStyle = false
	}

	if .getBucketFromInput != nil {
		// Below customization only apply if bucket name is provided
		,  := .getBucketFromInput(.Parameters)
		if  &&  != nil {
			 := awsmiddleware.GetRegion()
			if  := .updateEndpointFromConfig(, *, );  != nil {
				return , , 
			}
		}
	}

	return .HandleSerialize(, )
}

func ( updateEndpoint) ( *smithyhttp.Request,  string,  string) error {
	// do nothing if path style is enforced
	if .usePathStyle {
		return nil
	}

	if !hostCompatibleBucketName(.URL, ) {
		// bucket name must be valid to put into the host for accelerate operations.
		// For non-accelerate operations the bucket name can stay in the path if
		// not valid hostname.
		var  error
		if .useAccelerate {
			 = fmt.Errorf("bucket name %s is not compatible with S3", )
		}

		// No-Op if not using accelerate.
		return 
	}

	// accelerate is only supported if use path style is disabled
	if .useAccelerate {
		 := strings.Split(.URL.Host, ".")
		if len() < 3 {
			return fmt.Errorf("unable to update endpoint host for S3 accelerate, hostname invalid, %s", .URL.Host)
		}

		if [0] == "s3" || strings.HasPrefix([0], "s3-") {
			[0] = "s3-accelerate"
		}

		for  := 1; +1 < len(); ++ {
			if strings.EqualFold([], ) {
				 = append([:], [+1:]...)
				break
			}
		}

		// construct the url host
		.URL.Host = strings.Join(, ".")
	}

	// move bucket to follow virtual host style
	moveBucketNameToHost(.URL, )
	return nil
}

// updates endpoint to use virtual host styling
func ( *url.URL,  string) {
	.Host =  + "." + .Host
	removeBucketFromPath(, )
}

// remove bucket from url
func ( *url.URL,  string) {
	if strings.HasPrefix(.Path, "/"+) {
		// modify url path
		.Path = strings.Replace(.Path, "/"+, "", 1)

		// modify url raw path
		.RawPath = strings.Replace(.RawPath, "/"+httpbinding.EscapePath(, true), "", 1)
	}

	if .Path == "" {
		.Path = "/"
	}

	if .RawPath == "" {
		.RawPath = "/"
	}
}

// hostCompatibleBucketName returns true if the request should
// put the bucket in the host. This is false if the bucket is not
// DNS compatible or the EndpointResolver resolves an aws.Endpoint with
// HostnameImmutable member set to true.
//
// https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws#Endpoint.HostnameImmutable
func ( *url.URL,  string) bool {
	// Bucket might be DNS compatible but dots in the hostname will fail
	// certificate validation, so do not use host-style.
	if .Scheme == "https" && strings.Contains(, ".") {
		return false
	}

	// if the bucket is DNS compatible
	return dnsCompatibleBucketName()
}

// dnsCompatibleBucketName returns true if the bucket name is DNS compatible.
// Buckets created outside of the classic region MUST be DNS compatible.
func ( string) bool {
	if strings.Contains(, "..") {
		return false
	}

	// checks for `^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$` domain mapping
	if !(([0] > 96 && [0] < 123) || ([0] > 47 && [0] < 58)) {
		return false
	}

	for ,  := range [1:] {
		if !(( > 96 &&  < 123) || ( > 47 &&  < 58) ||  == 46 ||  == 45) {
			return false
		}
	}

	// checks for `^(\d+\.){3}\d+$` IPaddressing
	 := strings.SplitN(, ".", -1)
	if len() == 4 {
		for ,  := range  {
			if !(( > 47 &&  < 58) ||  == 46) {
				// we confirm that this is not a IP address
				return true
			}
		}
		// this is a IP address
		return false
	}

	return true
}