// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package stack

import (
	"fmt"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/copilot-cli/internal/pkg/deploy"
	"github.com/aws/copilot-cli/internal/pkg/deploy/upload/customresource"
	"github.com/aws/copilot-cli/internal/pkg/manifest"
	"github.com/aws/copilot-cli/internal/pkg/manifest/manifestinfo"
	"github.com/aws/copilot-cli/internal/pkg/template"
)

var awsSDKLayerForRegion = map[string]*string{
	"ap-northeast-1": aws.String("arn:aws:lambda:ap-northeast-1:249908578461:layer:AWSLambda-Node-AWS-SDK:15"),
	"us-east-1":      aws.String("arn:aws:lambda:us-east-1:668099181075:layer:AWSLambda-Node-AWS-SDK:15"),
	"ap-southeast-1": aws.String("arn:aws:lambda:ap-southeast-1:468957933125:layer:AWSLambda-Node-AWS-SDK:14"),
	"eu-west-1":      aws.String("arn:aws:lambda:eu-west-1:399891621064:layer:AWSLambda-Node-AWS-SDK:14"),
	"us-west-1":      aws.String("arn:aws:lambda:us-west-1:325793726646:layer:AWSLambda-Node-AWS-SDK:15"),
	"ap-northeast-2": aws.String("arn:aws:lambda:ap-northeast-2:296580773974:layer:AWSLambda-Node-AWS-SDK:14"),
	"ap-south-1":     aws.String("arn:aws:lambda:ap-south-1:631267018583:layer:AWSLambda-Node-AWS-SDK:14"),
	"ap-southeast-2": aws.String("arn:aws:lambda:ap-southeast-2:817496625479:layer:AWSLambda-Node-AWS-SDK:14"),
	"ca-central-1":   aws.String("arn:aws:lambda:ca-central-1:778625758767:layer:AWSLambda-Node-AWS-SDK:14"),
	"eu-central-1":   aws.String("arn:aws:lambda:eu-central-1:292169987271:layer:AWSLambda-Node-AWS-SDK:14"),
	"eu-west-2":      aws.String("arn:aws:lambda:eu-west-2:142628438157:layer:AWSLambda-Node-AWS-SDK:14"),
	"sa-east-1":      aws.String("arn:aws:lambda:sa-east-1:640010853179:layer:AWSLambda-Node-AWS-SDK:14"),
	"us-east-2":      aws.String("arn:aws:lambda:us-east-2:259788987135:layer:AWSLambda-Node-AWS-SDK:14"),
	"us-west-2":      aws.String("arn:aws:lambda:us-west-2:420165488524:layer:AWSLambda-Node-AWS-SDK:14"),
	"af-south-1":     aws.String("arn:aws:lambda:af-south-1:392341123054:layer:AWSLambda-Node-AWS-SDK:7"),
	"ap-east-1":      aws.String("arn:aws:lambda:ap-east-1:118857876118:layer:AWSLambda-Node-AWS-SDK:14"),
	"ap-northeast-3": aws.String("arn:aws:lambda:ap-northeast-3:961244031340:layer:AWSLambda-Node-AWS-SDK:14"),
	"eu-north-1":     aws.String("arn:aws:lambda:eu-north-1:642425348156:layer:AWSLambda-Node-AWS-SDK:14"),
	"eu-south-1":     aws.String("arn:aws:lambda:eu-south-1:426215560912:layer:AWSLambda-Node-AWS-SDK:7"),
	"eu-west-3":      aws.String("arn:aws:lambda:eu-west-3:959311844005:layer:AWSLambda-Node-AWS-SDK:14"),
	"me-south-1":     aws.String("arn:aws:lambda:me-south-1:507411403535:layer:AWSLambda-Node-AWS-SDK:10"),
}

// RequestDrivenWebService represents the configuration needed to create a CloudFormation stack from a request-drive web service manifest.
type RequestDrivenWebService struct {
	*appRunnerWkld
	manifest *manifest.RequestDrivenWebService
	app      deploy.AppInformation

	parser requestDrivenWebSvcReadParser
}

// RequestDrivenWebServiceConfig contains data required to initialize a request-driven web service stack.
type RequestDrivenWebServiceConfig struct {
	App                deploy.AppInformation
	Env                string
	Manifest           *manifest.RequestDrivenWebService
	RawManifest        []byte
	ArtifactBucketName string
	RuntimeConfig      RuntimeConfig
	Addons             NestedStackConfigurer
}

// NewRequestDrivenWebService creates a new RequestDrivenWebService stack from a manifest file.
func NewRequestDrivenWebService(cfg RequestDrivenWebServiceConfig) (*RequestDrivenWebService, error) {
	crs, err := customresource.RDWS(fs)
	if err != nil {
		return nil, fmt.Errorf("request-driven web service custom resources: %w", err)
	}
	cfg.RuntimeConfig.loadCustomResourceURLs(cfg.ArtifactBucketName, uploadableCRs(crs).convert())

	return &RequestDrivenWebService{
		appRunnerWkld: &appRunnerWkld{
			wkld: &wkld{
				name:               aws.StringValue(cfg.Manifest.Name),
				env:                cfg.Env,
				app:                cfg.App.Name,
				permBound:          cfg.App.PermissionsBoundary,
				artifactBucketName: cfg.ArtifactBucketName,
				rc:                 cfg.RuntimeConfig,
				image:              cfg.Manifest.ImageConfig.Image,
				rawManifest:        cfg.RawManifest,
				addons:             cfg.Addons,
				parser:             fs,
			},
			instanceConfig:    cfg.Manifest.InstanceConfig,
			imageConfig:       cfg.Manifest.ImageConfig,
			healthCheckConfig: cfg.Manifest.HealthCheckConfiguration,
		},
		app:      cfg.App,
		manifest: cfg.Manifest,
		parser:   fs,
	}, nil
}

// Template returns the CloudFormation template for the service parametrized for the environment.
func (s *RequestDrivenWebService) Template() (string, error) {
	crs, err := convertCustomResources(s.rc.CustomResourcesURL)
	if err != nil {
		return "", err
	}
	networkConfig := convertRDWSNetworkConfig(s.manifest.Network)
	addonsParams, err := s.addonsParameters()
	if err != nil {
		return "", err
	}
	addonsOutputs, err := s.addonsOutputs()
	if err != nil {
		return "", err
	}
	var layerARN, dnsDelegationRole, dnsName *string
	if s.manifest.Alias != nil {
		dnsDelegationRole, dnsName = convertAppInformation(s.app)
		layerARN = awsSDKLayerForRegion[s.rc.Region]
	}
	publishers, err := convertPublish(s.manifest.Publish(), s.rc.AccountID, s.rc.Region, s.app.Name, s.env, s.name)
	if err != nil {
		return "", fmt.Errorf(`convert "publish" field for service %s: %w`, s.name, err)
	}
	content, err := s.parser.ParseRequestDrivenWebService(template.WorkloadOpts{
		AppName:            s.wkld.app,
		EnvName:            s.env,
		WorkloadName:       s.name,
		SerializedManifest: string(s.rawManifest),
		EnvVersion:         s.rc.EnvVersion,
		Version:            s.rc.Version,

		Variables:            convertEnvVars(s.manifest.Variables),
		StartCommand:         s.manifest.StartCommand,
		Tags:                 s.manifest.Tags,
		NestedStack:          addonsOutputs,
		AddonsExtraParams:    addonsParams,
		EnableHealthCheck:    !s.healthCheckConfig.IsZero(),
		WorkloadType:         manifestinfo.RequestDrivenWebServiceType,
		Alias:                s.manifest.Alias,
		CustomResources:      crs,
		AWSSDKLayer:          layerARN,
		AppDNSDelegationRole: dnsDelegationRole,
		AppDNSName:           dnsName,
		Network:              networkConfig,

		Publish:                  publishers,
		ServiceDiscoveryEndpoint: s.rc.ServiceDiscoveryEndpoint,

		Observability: template.ObservabilityOpts{
			Tracing: strings.ToUpper(aws.StringValue(s.manifest.Observability.Tracing)),
		},
		PermissionsBoundary:  s.permBound,
		Private:              aws.BoolValue(s.manifest.Private.Basic) || s.manifest.Private.Advanced.Endpoint != nil,
		AppRunnerVPCEndpoint: s.manifest.Private.Advanced.Endpoint,
		Count:                s.manifest.Count,
		Secrets:              convertSecrets(s.manifest.RequestDrivenWebServiceConfig.Secrets),
	})
	if err != nil {
		return "", err
	}
	return content.String(), nil
}

// SerializedParameters returns the CloudFormation stack's parameters serialized to a JSON document.
func (s *RequestDrivenWebService) SerializedParameters() (string, error) {
	return serializeTemplateConfig(s.wkld.parser, s)
}