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

package multi_env_app_test

import (
	"fmt"
	"net/http"
	"os"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"

	"github.com/aws/copilot-cli/e2e/internal/client"
)

var (
	initErr error
)

var _ = Describe("Multiple Env App", func() {
	Context("when creating a new app", Ordered, func() {
		BeforeAll(func() {
			_, initErr = cli.AppInit(&client.AppInitRequest{
				AppName: appName,
				Tags: map[string]string{
					"e2e-test": "multi-env",
				},
			})
		})

		It("app init succeeds", func() {
			Expect(initErr).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 adding cross account environments", Ordered, func() {
		var (
			testEnvInitErr error
			prodEnvInitErr error
		)
		BeforeAll(func() {
			_, testEnvInitErr = cli.EnvInit(&client.EnvInitRequest{
				AppName: appName,
				EnvName: "test",
				Profile: "test",
			})

			_, prodEnvInitErr = cli.EnvInit(&client.EnvInitRequest{
				AppName: appName,
				EnvName: "prod",
				Profile: "prod",
			})

		})

		It("env init should succeed for test and prod envs", func() {
			fmt.Println(testEnvInitErr)
			fmt.Println(prodEnvInitErr)
			Expect(testEnvInitErr).NotTo(HaveOccurred())
			Expect(prodEnvInitErr).NotTo(HaveOccurred())
		})

		It("should create environment manifests", func() {
			Expect("./copilot/environments/test/manifest.yml").Should(BeAnExistingFile())
			Expect("./copilot/environments/prod/manifest.yml").Should(BeAnExistingFile())
		})

		It("env ls should list both envs", func() {
			envListOutput, err := cli.EnvList(appName)
			Expect(err).NotTo(HaveOccurred())
			Expect(len(envListOutput.Envs)).To(Equal(2))
			envs := map[string]client.EnvDescription{}
			for _, env := range envListOutput.Envs {
				envs[env.Name] = env
				Expect(env.ExecutionRole).NotTo(BeEmpty())
				Expect(env.ManagerRole).NotTo(BeEmpty())
			}

			Expect(envs["test"]).NotTo(BeNil())
			Expect(envs["prod"]).NotTo(BeNil())

			// Make sure, for the sake of coverage, these are cross account,
			// cross region environments if we're not doing a dryrun.
			if os.Getenv("DRYRUN") != "true" {
				Expect(envs["test"].Region).NotTo(Equal(envs["prod"].Region))
				Expect(envs["test"].Account).NotTo(Equal(envs["prod"].Account))
			}
		})

		It("should show only bootstrap resources in env show", func() {
			testEnvShowOutput, testEnvShowError := cli.EnvShow(&client.EnvShowRequest{
				AppName: appName,
				EnvName: "test",
			})
			prodEnvShowOutput, prodEnvShowError := cli.EnvShow(&client.EnvShowRequest{
				AppName: appName,
				EnvName: "prod",
			})
			Expect(testEnvShowError).NotTo(HaveOccurred())
			Expect(prodEnvShowError).NotTo(HaveOccurred())

			Expect(testEnvShowOutput.Environment.Name).To(Equal("test"))
			Expect(testEnvShowOutput.Environment.App).To(Equal(appName))
			Expect(prodEnvShowOutput.Environment.Name).To(Equal("prod"))
			Expect(prodEnvShowOutput.Environment.App).To(Equal(appName))

			// Contains only bootstrap resources - two IAM roles.
			Expect(len(testEnvShowOutput.Resources)).To(Equal(2))
			Expect(len(prodEnvShowOutput.Resources)).To(Equal(2))
		})
	})

	Context("when deploying the environments", Ordered, func() {
		var testEnvDeployErr, prodEnvDeployErr error
		BeforeAll(func() {
			_, testEnvDeployErr = cli.EnvDeploy(&client.EnvDeployRequest{
				AppName: appName,
				Name:    "test",
			})
			_, prodEnvDeployErr = cli.EnvDeploy(&client.EnvDeployRequest{
				AppName: appName,
				Name:    "prod",
			})
		})

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

	Context("when adding a svc", Ordered, func() {
		var (
			frontEndInitErr error
		)
		BeforeAll(func() {
			_, frontEndInitErr = cli.SvcInit(&client.SvcInitRequest{
				Name:       "front-end",
				SvcType:    "Load Balanced Web Service",
				Dockerfile: "./front-end/Dockerfile",
				SvcPort:    "80",
			})
		})

		It("svc init should succeed", func() {
			Expect(frontEndInitErr).NotTo(HaveOccurred())
		})

		It("svc init should create a svc manifest", func() {
			Expect("./copilot/front-end/manifest.yml").Should(BeAnExistingFile())
		})

		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("front-end"))
		})

		It("svc package should output a cloudformation template and params file", func() {
			Skip("not implemented yet")
		})
	})

	Context("when deploying a svc to test and prod envs", Ordered, func() {
		var (
			testDeployErr    error
			prodEndDeployErr error
			svcName          string
		)
		BeforeAll(func() {
			svcName = "front-end"
			_, testDeployErr = cli.SvcDeploy(&client.SvcDeployInput{
				Name:     svcName,
				EnvName:  "test",
				ImageTag: "gallopinggurdey",
			})

			_, prodEndDeployErr = cli.SvcDeploy(&client.SvcDeployInput{
				Name:     svcName,
				EnvName:  "prod",
				ImageTag: "gallopinggurdey",
			})
		})

		It("svc deploy should succeed to both environment", func() {
			Expect(testDeployErr).NotTo(HaveOccurred())
			Expect(prodEndDeployErr).NotTo(HaveOccurred())
		})

		It("svc show should include a valid URL and description for test and prod envs", func() {
			svc, svcShowErr := cli.SvcShow(&client.SvcShowRequest{
				AppName: appName,
				Name:    svcName,
			})
			Expect(svcShowErr).NotTo(HaveOccurred())
			Expect(len(svc.Routes)).To(Equal(2))
			// Group routes by environment
			envRoutes := map[string]client.SvcShowRoutes{}
			for _, route := range svc.Routes {
				envRoutes[route.Environment] = route
			}

			Expect(len(svc.ServiceDiscoveries)).To(Equal(2))
			var envs, endpoints, wantedEndpoints []string
			for _, sd := range svc.ServiceDiscoveries {
				envs = append(envs, sd.Environment[0])
				endpoints = append(endpoints, sd.Endpoint)
				wantedEndpoints = append(wantedEndpoints, fmt.Sprintf("%s.%s.%s.local:80", svc.SvcName, sd.Environment[0], appName))
			}
			Expect(envs).To(ConsistOf("test", "prod"))
			Expect(endpoints).To(ConsistOf(wantedEndpoints))

			// Call each environment's endpoint and ensure it returns a 200
			for _, env := range []string{"test", "prod"} {
				route := envRoutes[env]
				Expect(route.Environment).To(Equal(env))
				Eventually(func() (int, error) {
					resp, fetchErr := http.Get(route.URL)
					return resp.StatusCode, fetchErr
				}, "30s", "1s").Should(Equal(200))
			}
		})

		It("svc logs should display logs", func() {
			for _, envName := range []string{"test", "prod"} {
				var svcLogs []client.SvcLogsOutput
				var svcLogsErr error
				Eventually(func() ([]client.SvcLogsOutput, error) {
					svcLogs, svcLogsErr = cli.SvcLogs(&client.SvcLogsRequest{
						AppName: appName,
						Name:    svcName,
						EnvName: envName,
						Since:   "1h",
					})
					return svcLogs, svcLogsErr
				}, "60s", "10s").ShouldNot(BeEmpty())

				for _, logLine := range svcLogs {
					Expect(logLine.Message).NotTo(Equal(""))
					Expect(logLine.LogStreamName).NotTo(Equal(""))
					Expect(logLine.Timestamp).NotTo(Equal(0))
					Expect(logLine.IngestionTime).NotTo(Equal(0))
				}
			}
		})

		It("env show should display info for test and prod envs", func() {
			envs := map[string]client.EnvDescription{}
			for _, envName := range []string{"test", "prod"} {
				envShowOutput, envShowErr := cli.EnvShow(&client.EnvShowRequest{
					AppName: appName,
					EnvName: envName,
				})
				Expect(envShowErr).NotTo(HaveOccurred())
				Expect(envShowOutput.Environment.Name).To(Equal(envName))
				Expect(envShowOutput.Environment.App).To(Equal(appName))

				Expect(len(envShowOutput.Services)).To(Equal(1))
				Expect(envShowOutput.Services[0].Name).To(Equal(svcName))
				Expect(envShowOutput.Services[0].Type).To(Equal("Load Balanced Web Service"))

				Expect(len(envShowOutput.Tags)).To(Equal(3))
				Expect(envShowOutput.Tags["copilot-application"]).To(Equal(appName))
				Expect(envShowOutput.Tags["copilot-environment"]).To(Equal(envName))
				Expect(envShowOutput.Tags["e2e-test"]).To(Equal("multi-env"))

				envs[envShowOutput.Environment.Name] = envShowOutput.Environment
			}
			Expect(envs["test"]).NotTo(BeNil())
			Expect(envs["prod"]).NotTo(BeNil())
			if os.Getenv("DRYRUN") != "true" {
				Expect(envs["test"].Region).NotTo(Equal(envs["prod"].Region))
				Expect(envs["test"].Account).NotTo(Equal(envs["prod"].Account))
			}
			Expect(envs["test"].ExecutionRole).NotTo(Equal(envs["prod"].ExecutionRole))
			Expect(envs["test"].ManagerRole).NotTo(Equal(envs["prod"].ExecutionRole))
		})
	})

	Context("when setting up a pipeline", func() {
		It("pipeline init should create a pipeline manifest", func() {
			Skip("not implemented yet")
		})

		It("pipeline deploy should create a pipeline", func() {
			Skip("not implemented yet")
		})
	})

	Context("when pushing a change to the pipeline", func() {
		It("the change should be propagated to test and prod environments", func() {
			Skip("not implemented yet")
		})
	})

})
