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

package isolated_test

import (
	"errors"
	"fmt"
	"net/http"

	"github.com/aws/copilot-cli/e2e/internal/client"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("Isolated", func() {
	Context("when creating a new app", Ordered, func() {
		var appInitErr error
		BeforeAll(func() {
			_, appInitErr = cli.AppInit(&client.AppInitRequest{
				AppName: appName,
				Tags: map[string]string{
					"e2e-test": "isolated",
				},
			})
		})

		It("app init succeeds", func() {
			Expect(appInitErr).NotTo(HaveOccurred())
		})
		It("app init creates a copilot directory", func() {
			Expect("./copilot").Should(BeADirectory())
		})
		It("app ls includes new app", func() {
			Eventually(cli.AppList, "30s", "5s").Should(ContainSubstring(appName))
		})
		It("app show includes app name", func() {
			appShowOutput, err := cli.AppShow(appName)
			Expect(err).NotTo(HaveOccurred())
			Expect(appShowOutput.Name).To(Equal(appName))
			Expect(appShowOutput.URI).To(BeEmpty())
		})
	})

	Context("when deploying resources to be imported", func() {
		It("vpc stack exists", func() {
			err := aws.WaitStackCreateComplete(vpcStackName)
			Expect(err).NotTo(HaveOccurred())
		})
		It("parse vpc stack output", func() {
			outputs, err := aws.VPCStackOutput(vpcStackName)
			Expect(err).NotTo(HaveOccurred(), "get VPC stack output")
			for _, output := range outputs {
				switch output.OutputKey {
				case "PrivateSubnets":
					vpcImport.PrivateSubnetIDs = output.OutputValue
				case "VpcId":
					vpcImport.ID = output.OutputValue
				}
			}
			if vpcImport.ID == "" || vpcImport.PrivateSubnetIDs == "" {
				err = errors.New("resources are not configured properly")
			}
			Expect(err).NotTo(HaveOccurred(), "invalid vpc stack output")
		})
	})

	Context("when adding environment with imported vpc resources", Ordered, func() {
		var testEnvInitErr error
		BeforeAll(func() {
			_, testEnvInitErr = cli.EnvInit(&client.EnvInitRequest{
				AppName:       appName,
				EnvName:       envName,
				Profile:       envName,
				VPCImport:     vpcImport,
				CustomizedEnv: true,
			})
		})
		It("env init should succeed for 'test' env", func() {
			Expect(testEnvInitErr).NotTo(HaveOccurred())
		})
	})

	Context("when deploying the environment", Ordered, func() {
		var testEnvDeployErr error
		BeforeAll(func() {
			_, testEnvDeployErr = cli.EnvDeploy(&client.EnvDeployRequest{
				AppName: appName,
				Name:    envName,
			})
		})
		It("should succeed", func() {
			Expect(testEnvDeployErr).NotTo(HaveOccurred())
		})
		It("env ls should list test env", func() {
			envListOutput, err := cli.EnvList(appName)
			Expect(err).NotTo(HaveOccurred())
			Expect(len(envListOutput.Envs)).To(Equal(1))
			env := envListOutput.Envs[0]
			Expect(env.Name).To(Equal(envName))
			Expect(env.ExecutionRole).NotTo(BeEmpty())
			Expect(env.ManagerRole).NotTo(BeEmpty())
		})
	})

	Context("when creating a backend service in private subnets", Ordered, func() {
		var initErr error
		BeforeAll(func() {
			_, initErr = cli.SvcInit(&client.SvcInitRequest{
				Name:       svcName,
				SvcType:    "Backend Service",
				Dockerfile: "./backend/Dockerfile",
				SvcPort:    "80",
			})
		})

		It("should not return an error", func() {
			Expect(initErr).NotTo(HaveOccurred())
		})
		It("svc ls should list the svc", func() {
			svcList, svcListError := cli.SvcList(appName)
			Expect(svcListError).NotTo(HaveOccurred())
			Expect(len(svcList.Services)).To(Equal(1))
			Expect(svcList.Services[0].Name).To(Equal("backend"))
		})
	})

	Context("when deploying a svc to 'test' env", Ordered, func() {
		var testEnvDeployErr error
		BeforeAll(func() {
			_, testEnvDeployErr = cli.SvcDeploy(&client.SvcDeployInput{
				Name:    svcName,
				EnvName: envName,
			})
		})

		It("svc deploy should succeed", func() {
			Expect(testEnvDeployErr).NotTo(HaveOccurred())
		})
	})

	Context("when running svc show to retrieve the service configuration, resources, and endpoint, then querying the service", Ordered, func() {
		var (
			svc          *client.SvcShowOutput
			svcShowError error
		)
		BeforeAll(func() {
			svc, svcShowError = cli.SvcShow(&client.SvcShowRequest{
				Name:      svcName,
				AppName:   appName,
				Resources: true,
			})
		})

		It("should not return an error", func() {
			Expect(svcShowError).NotTo(HaveOccurred())
		})

		It("should return correct configuration", func() {
			Expect(svc.SvcName).To(Equal(svcName))
			Expect(svc.AppName).To(Equal(appName))
			Expect(len(svc.Configs)).To(Equal(1))
			Expect(svc.Configs[0].Environment).To(Equal(envName))
			Expect(svc.Configs[0].CPU).To(Equal("256"))
			Expect(svc.Configs[0].Memory).To(Equal("512"))
			Expect(svc.Configs[0].Port).To(Equal("80"))
			Expect(svc.Routes[0].URL).To(ContainSubstring(fmt.Sprintf("http://%s.%s.%s.internal", svcName, envName, appName)))
		})
	})

	Context("when running `svc status`", func() {
		It("it should include the service, tasks, and alarm status", func() {
			svc, svcStatusErr := cli.SvcStatus(&client.SvcStatusRequest{
				AppName: appName,
				Name:    svcName,
				EnvName: envName,
			})
			Expect(svcStatusErr).NotTo(HaveOccurred())
			// Service should be active.
			Expect(svc.Service.Status).To(Equal("ACTIVE"))
			// Desired count should be minimum auto scaling number.
			Expect(svc.Service.DesiredCount).To(Equal(int64(1)))
			// Should have correct number of running tasks.
			Expect(len(svc.Tasks)).To(Equal(1))
		})
	})

	Context("when running `env show --resources`", func() {
		var envShowOutput *client.EnvShowOutput
		var envShowErr error
		It("should show internal ALB", func() {
			envShowOutput, envShowErr = cli.EnvShow(&client.EnvShowRequest{
				AppName: appName,
				EnvName: envName,
			})
			Expect(envShowErr).NotTo(HaveOccurred())
			Expect(envShowOutput.Resources).To(ContainElement(HaveKeyWithValue("type", "AWS::ElasticLoadBalancingV2::LoadBalancer")))
		})
	})

	Context("when trying to reach the LB DNS", func() {
		It("it is not reachable", func() {
			var resp *http.Response
			var fetchErr error
			Eventually(func() (*http.Response, error) {
				resp, fetchErr = http.Get(fmt.Sprintf("http://%s.%s.%s.internal", svcName, envName, appName))
				return resp, fetchErr
			}, "60s", "1s")
			Expect(resp).To(BeNil())
		})
	})

	Context("when `curl`ing the LB DNS from within the container", func() {
		It("session manager should be installed", func() {
			// Use custom SSM plugin as the public version is not compatible to Alpine Linux.
			err := client.BashExec("chmod +x ./session-manager-plugin")
			Expect(err).NotTo(HaveOccurred())
			err = client.BashExec("mv ./session-manager-plugin /bin/session-manager-plugin")
			Expect(err).NotTo(HaveOccurred())
		})
		It("is reachable", func() {
			_, svcExecErr := cli.SvcExec(&client.SvcExecRequest{
				Name:    svcName,
				AppName: appName,
				Command: fmt.Sprintf(`/bin/sh -c "curl 'http://%s.%s.%s.internal'"`, svcName, envName, appName),
				EnvName: envName,
			})
			Expect(svcExecErr).NotTo(HaveOccurred())
		})
	})
})