package integration_test

import (
	"context"
	"database/sql"
	_ "embed"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"time"

	"edge-infra.dev/pkg/edge/api/apierror"
	"edge-infra.dev/pkg/edge/api/graph/mapper"
	"edge-infra.dev/pkg/edge/api/graph/model"
	sqlquery "edge-infra.dev/pkg/edge/api/sql"
	"edge-infra.dev/pkg/edge/api/utils"
	"edge-infra.dev/test/framework/integration"

	helmApi "github.com/fluxcd/helm-controller/api/v2"
	"github.com/udacity/graphb"

	"k8s.io/apimachinery/pkg/util/intstr"
)

const (
	testHelmEdgeID = "be8536ff-d463-4aff-8fa9-fe81fec1ddc1"
	helmCharts     = `
apiVersion: v1
entries:
 test-helm-chart1:
 - apiVersion: v2
   version: 0.0.1
   name: test-helm-chart1
   urls:
   - "http://%s/chart.tgz"
 test-helm-chart2:
 - apiVersion: v2
   version: 0.0.2
   name: test-helm-chart2
   urls:
   - "http://%s/chart.tgz"
`
	readme = `
# Introduction

This chart is a umbrella chart for emerald services including selling service, pos config etc.

# Prerequisites
Before deploying the chart you need to:
`
	updateConfigValues = `
global:
 deploymentMode: store
 gcloud:
   serviceAccount:
     enabled: true
     secret: ewog-testing`
	schemaData = `
	{
		"$schema": "http://json-schema.org/schema#",
		"type": "object",
		"properties": {
			"ingress": {
				"type": "object",
				"properties": {
					"namespace": {
						"type": "string",
						"default": "selling-services",
						"pattern": "^[a-z]+[-]*[a-z]*$"
					},
					"services": {
						"type": "array",
						"items": {
							"type": "object",
							"properties": {
								"service": {
									"type": "object",
									"properties": {
										"name": {
											"type": "string",
											"pattern": "^[a-z]+[-]*[a-z]*$"
										},
										"path": {
											"type": "string",
											"pattern": "^[A-Za-z0-9_.\/-]*$"
										},
										"port": {
											"type": "integer",
											"pattern": "^[1-9]*$"
										}
									}
								}
							}
						}
					}
				}
			},
			"selling-configuration": {
				"type": "object",
				"properties": {
					"app": {
						"type": "object",
						"properties": {
							"image": {
								"type": "string",
								"default": "ncr-emeraldedge-docker-dev.jfrog.io/selling-configuration",
								"pattern": "^[A-Za-z0-9_.\/-]*$"
							},
							"replicaCount": {
								"type": "integer",
								"default": 1,
								"pattern": "^[1-9]*$"
							},
							"version": {
								"type": "string",
								"default": "1.0.0",
								"pattern": "^[0-9.]*$"
							}
						}
					},
					"livenessProbe": {
						"type": "object",
						"properties": {
							"enabled": {
								"type": "boolean",
								"default": true
							},
							"healthCheckAPI": {
								"type": "string",
								"default": "selling-configuration/health",
								"pattern": "^[A-Za-z0-9_.\/-]*$"
							}
						}
					},
					"namespace": {
						"type": "string",
						"default": "selling-services",
						"pattern": "^[a-z]+[-]*[a-z]*$"
					}
				}
			},
			"selling-configuration-pgsql": {
				"type": "object",
				"properties": {
					"app": {
						"type": "object",
						"properties": {
							"image": {
								"type": "string",
								"default": "ncr-emeraldedge-docker-dev.jfrog.io/selling-conf-postgresql",
								"pattern": "^[A-Za-z0-9_.\/-]*$"
							},
							"replicaCount": {
								"type": "integer",
								"default": 1,
								"pattern": "^[1-9]*$"
							},
							"version": {
								"type": "string",
								"default": "1.0.0",
								"pattern": "^[0-9.]*$"
							}
						}
					},
					"namespace": {
						"type": "string",
						"default": "selling-services",
						"pattern": "^[a-z]+[-]*[a-z]*$"
					}
				}
			},
			"selling-engine": {
				"type": "object",
				"properties": {
					"app": {
						"type": "object",
						"required": ["image", "replicaCount", "version"],
						"properties": {
							"image": {
								"type": "string",
								"default": "ncr-emeraldedge-docker-dev.jfrog.io/selling_engine",
								"pattern": "^[A-Za-z0-9_.\/-]*$"
							},
							"replicaCount": {
								"type": "integer",
								"default": 1,
								"pattern": "^[1-9]*$"
							},
							"version": {
								"type": "string",
								"default": "1.0.1",
								"pattern": "^[0-9.]*$"
							}
						}
					},
					"livenessProbe": {
						"type": "object",
						"properties": {
							"enabled": {
								"type": "boolean",
								"default": true
							},
							"healthCheckAPI": {
								"type": "string",
								"default": "selling-execution/health",
								"pattern": "^[A-Za-z0-9_.\/-]*$"
							}
						}
					},
					"namespace": {
						"type": "string",
						"default": "selling-services",
						"pattern": "^[a-z]+[-]*[a-z]*$"
					}
				}
			}
		}
	}
	`
)

//go:embed testdata/alpine-0.2.0.tgz
var zippedChart string

func (s Suite) TestCreateHelmRelease() {
	integration.SkipIf(s.Framework)
	srv := s.createHelmRepoTestServer()
	helmURL := srv.URL
	name := fmt.Sprintf("create-helmrelease-%s", s.testTime)
	var response struct{ CreateOrUpdateSecretManagerSecret bool }
	mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, testHelmSecret, "edge", "helm-repository", "tenant", []model.KeyValues{
		{Key: "helm_repo_name", Value: "podinfo"},
		{Key: "helmUrl", Value: helmURL},
	})
	ResolverClient.MustPost(mutation, &response)
	s.NotNil(response.CreateOrUpdateSecretManagerSecret)
	s.Equal(response.CreateOrUpdateSecretManagerSecret, true)

	var res struct{ DefaultSchemaConfig *model.HelmConfig }
	query := defaultSchemaConfigQuery(testHelmSecret, testOrgBannerEdgeID, testHelmChart, testHelmVersion)
	s.NoError(GraphqlRetry(query, &res, func() bool { return res.DefaultSchemaConfig != nil }))
	s.NotNil(res.DefaultSchemaConfig)
	s.NotEmpty(res.DefaultSchemaConfig.ConfigVals)

	// Test that a helm release is created by passing in a clusterEdgeID and no bannerEdgeID
	var resp struct{ CreateHelmRelease bool }
	namespace := fmt.Sprintf("helmrelease-%d", time.Now().UnixNano())
	mutation = s.createHelmReleaseMutation("", testClusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "")
	ResolverClient.MustPost(mutation, &resp)
	s.True(resp.CreateHelmRelease)

	// Test that a helm release is created by passing in a bannerEdgeID and no clusterEdgeID
	namespace = fmt.Sprintf("helmrelease-%d", time.Now().UnixNano())
	mutation = s.createHelmReleaseMutation(testOrgBannerEdgeID, "", name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "")
	ResolverClient.MustPost(mutation, &resp)
	s.True(resp.CreateHelmRelease)

	// Test that a helm release is not created if neither ID is passed into the mutation
	namespace = fmt.Sprintf("helmrelease-%d", time.Now().UnixNano())
	mutation = s.createHelmReleaseMutation("", "", name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "")
	s.Error(ResolverClient.Post(mutation, &resp))
	s.False(resp.CreateHelmRelease)
}

func (s Suite) TestCreateExternalSecretHelmRelease() {
	integration.SkipIf(s.Framework)
	srv := s.createHelmRepoTestServer()
	helmURL := srv.URL
	namespace := fmt.Sprintf("secret-ns-%d", time.Now().UnixNano())
	name := fmt.Sprintf("external-secret-helmrelease-%d", time.Now().UnixNano())

	var resp struct{ CreateHelmRelease bool }
	testSecret1 := "test-secret-1"
	testSecret2 := "test-secret-2"

	var response struct{ CreateOrUpdateSecretManagerSecret bool }
	mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, testSecret1, "edge", "docker-registry", "tenant", []model.KeyValues{
		{Key: "docker-username", Value: "jd250001"},
		{Key: "docker-password", Value: "password1"},
		{Key: "docker-server", Value: "https://example.com"},
	})
	ResolverClient.MustPost(mutation, &response)
	s.NotNil(response.CreateOrUpdateSecretManagerSecret)
	s.Equal(response.CreateOrUpdateSecretManagerSecret, true)

	mutation = createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, testSecret2, "edge", "helm-repository", "tenant", []model.KeyValues{
		{Key: "helm_repo_name", Value: "podinfo"},
		{Key: "helmUrl", Value: helmURL},
	})
	ResolverClient.MustPost(mutation, &response)
	s.NotNil(response.CreateOrUpdateSecretManagerSecret)
	s.Equal(response.CreateOrUpdateSecretManagerSecret, true)

	mutation = createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, testHelmSecret, "edge", "helm-repository", "tenant", []model.KeyValues{
		{Key: "helm_repo_name", Value: "podinfo"},
		{Key: "helmUrl", Value: helmURL},
	})
	ResolverClient.MustPost(mutation, &response)
	s.NotNil(response.CreateOrUpdateSecretManagerSecret)
	s.Equal(response.CreateOrUpdateSecretManagerSecret, true)

	clusterEdgeID := "3396a52c-6a22-4049-9593-5a63b596a205"
	mutation = s.createHelmReleaseMutation(testOrgBannerEdgeID, clusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", testSecret1, testSecret2)
	ResolverClient.MustPost(mutation, &resp)
	s.True(resp.CreateHelmRelease)

	helmEdgeID, err := s.getHelmEdgeIDByNameAndID(s.ctx, name, nil, &testOrgBannerEdgeID)
	s.NoError(err)

	var res struct{ HelmWorkload *model.HelmWorkload }
	query := helmWorkloadQuery(clusterEdgeID, helmEdgeID)
	ResolverClient.MustPost(query, &res)
	s.NotNil(res.HelmWorkload)
	s.Equal(testHelmVersion, res.HelmWorkload.HelmChartVersion)
	s.NotNil(res.HelmWorkload.ConfigValues)
	s.Equal("", *res.HelmWorkload.ConfigValues)
	s.NotNil(res.HelmWorkload.InstalledBy)
	s.Equal("acct:emerald-edge-dev@testing", *res.HelmWorkload.InstalledBy)
}

func (s Suite) TestCreateInvalidHelmRelease() {
	integration.SkipIf(s.Framework)
	var resp struct{ CreateHelmRelease bool }

	name := fmt.Sprintf("Create-helmrelease-%s", s.testTime)
	namespace := fmt.Sprintf("helmrelease-%s", s.testTime)
	mutation := s.createHelmReleaseMutation(testOrgBannerEdgeID, testClusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "")
	err := ResolverClient.Post(mutation, &resp)
	s.Error(err)
	s.Contains(err.Error(), "invalid name for helm resource")
	s.False(resp.CreateHelmRelease)

	name = fmt.Sprintf("create-helmrelease-%s", s.testTime)
	namespace = fmt.Sprintf("Helmrelease-%s", s.testTime)
	mutation = s.createHelmReleaseMutation(testOrgBannerEdgeID, testClusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "")
	err = ResolverClient.Post(mutation, &resp)
	s.Error(err)
	s.Contains(err.Error(), "invalid namespace for helm resource")
	s.False(resp.CreateHelmRelease)
}

func (s Suite) TestDeleteHelmRelease() {
	integration.SkipIf(s.Framework)

	// helmEdgeID for workload named "test-deleting-helm-workload"
	helmEdgeID := "29b8709a-2288-4054-829a-6dbfa8cf9eec"
	var deleteResponse struct{ DeleteHelmRelease bool }
	mutation := deleteHelmReleaseMutation(helmEdgeID, "", "")
	ResolverClient.MustPost(mutation, &deleteResponse)
	s.True(deleteResponse.DeleteHelmRelease)
}

func (s Suite) TestDeleteHelmRelease_Invalid() {
	integration.SkipIf(s.Framework)
	name := "duplicate-workload-name"

	// Case 1: Neither a helmEdgeId nor name and clusterEdgeId are provided
	var deleteResponse struct{ DeleteHelmRelease bool }
	mutation := deleteHelmReleaseMutation("", "", "")
	err := ResolverClient.Post(mutation, &deleteResponse)
	s.Error(err)
	s.False(deleteResponse.DeleteHelmRelease)

	// Case 2: A helmReleaseName is provided but a clusterEdgeId is not provided
	mutation = deleteHelmReleaseMutation("", "", name)
	err = ResolverClient.Post(mutation, &deleteResponse)
	s.Error(err)
	s.False(deleteResponse.DeleteHelmRelease)

	// Case 3: Both helmReleaseName and clusterEdgeId are provided but there are multiple matching
	// workloads for the provided inputs
	mutation = deleteHelmReleaseMutation("", clusterEdgeID, name)
	err = ResolverClient.Post(mutation, &deleteResponse)
	s.Error(err)
	s.False(deleteResponse.DeleteHelmRelease)

	// Case 4: A workload is deployed to multiple (more than 1) clusters via labels
	//
	// helmEdgeID for a workload that is deployed to 2 clusters via labels
	helmEdgeID := "aa015539-3de1-4ec9-bc98-4c9dcc8841e0"
	mutation = deleteHelmReleaseMutation(helmEdgeID, "", "")
	err = ResolverClient.Post(mutation, &deleteResponse)
	s.Error(err)
	s.False(deleteResponse.DeleteHelmRelease)
}

func (s Suite) TestCreateOrUpdateBannerHelmRepository() {
	integration.SkipIf(s.Framework)
	name := fmt.Sprintf("create-helmrepo-%s", s.testTime)

	var response struct{ CreateOrUpdateBannerHelmRepository bool }
	mutation := createOrUpdateBannerHelmRepositoryMutation(testOrgBannerEdgeID, name, testHelmRepoURL, testHelmSecret)
	ResolverClient.MustPost(mutation, &response)
	s.True(response.CreateOrUpdateBannerHelmRepository, "unable to create HelmRepository")

	mutation = createOrUpdateBannerHelmRepositoryMutation(testOrgBannerEdgeID, name, testHelmRepoURL, testHelmSecret)
	ResolverClient.MustPost(mutation, &response)
	s.True(response.CreateOrUpdateBannerHelmRepository, "unable to update HelmRepository")
}

func (s Suite) TestCreateOrUpdateInvalidBannerHelmRepository() {
	integration.SkipIf(s.Framework)
	name := fmt.Sprintf("Create-helmrepo-%s", s.testTime)

	var response struct{ CreateOrUpdateBannerHelmRepository bool }
	mutation := createOrUpdateBannerHelmRepositoryMutation(testOrgBannerEdgeID, name, testHelmRepoURL, testHelmSecret)
	err := ResolverClient.Post(mutation, &response)
	s.Error(err)
	s.Contains(err.Error(), "invalid name for helm repository")
	s.False(response.CreateOrUpdateBannerHelmRepository)
}

func (s Suite) TestDeleteBannerHelmRepository() {
	integration.SkipIf(s.Framework)
	name := fmt.Sprintf("delete-helmrepo-%s", s.testTime)

	var response struct{ CreateOrUpdateBannerHelmRepository bool }
	mutation := createOrUpdateBannerHelmRepositoryMutation(testOrgBannerEdgeID, name, testHelmRepoURL, testHelmSecret)
	ResolverClient.MustPost(mutation, &response)
	s.True(response.CreateOrUpdateBannerHelmRepository, "unable to create or update HelmRepository")
}

func (s Suite) TestUpdateHelmRelease_Invalid() {
	integration.SkipIf(s.Framework)
	name := "duplicate-workload-name"

	// Case 1: Neither a helmEdgeID nor helmReleaseName and clusterEdgeId are provided
	var updatedResponse struct{ UpdateHelmRelease bool }
	mutation := updateHelmReleaseMutation("", "", "", testHelmUpdatedVersion, "", nil, nil)
	err := ResolverClient.Post(mutation, &updatedResponse)
	s.Error(err)
	s.False(updatedResponse.UpdateHelmRelease)

	// Case 2: A helmReleaseName is provided but a clusterEdgeId is not provided
	mutation = updateHelmReleaseMutation("", "", name, testHelmUpdatedVersion, "", nil, nil)
	err = ResolverClient.Post(mutation, &updatedResponse)
	s.Error(err)
	s.False(updatedResponse.UpdateHelmRelease)

	// Case 3: Both helmReleaseName and clusterEdgeId are provided but there are multiple matching
	// workloads for the provided inputs
	mutation = updateHelmReleaseMutation("", testClusterEdgeID, name, testHelmUpdatedVersion, "", nil, nil)
	err = ResolverClient.Post(mutation, &updatedResponse)
	s.Error(err)
	s.False(updatedResponse.UpdateHelmRelease)
}

func (s Suite) TestUpdateHelmRelease() {
	integration.SkipIf(s.Framework)
	helmURL := testHelmRepoURL
	if !integration.IsIntegrationTest() {
		srv := s.createHelmRepoTestServer()
		helmURL = srv.URL
	}

	namespace := fmt.Sprintf("helmrelease-%s", s.testTime)
	chartSecret := fmt.Sprintf("chart-secret-%s", s.testTime)
	name := "test-helm-release"
	secrets := []string{"test-secret-1", "test-secret-2"}

	testLabelEdgeIDs := []string{"442f2e77-279d-45af-acae-4ec5458b7e00"}

	var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool }
	mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{
		{Key: "helm_repo_name", Value: testHelmRepo},
		{Key: "helmUrl", Value: helmURL},
	})
	ResolverClient.MustPost(mutation, &secretResponse)
	s.True(secretResponse.CreateOrUpdateSecretManagerSecret)

	var secretManagerQuery struct{ SecretManagerSecret model.SecretManagerResponse }
	secretQuery := getSecretManagerSecret(chartSecret, testOrgBannerEdgeID, true)
	ResolverClient.MustPost(secretQuery, &secretManagerQuery)
	s.Equal(len(secretManagerQuery.SecretManagerSecret.Values), 2)

	var response struct{ DefaultSchemaConfig *model.HelmConfig }
	query := defaultSchemaConfigQuery(chartSecret, testOrgBannerEdgeID, testHelmChart, testHelmVersion)
	s.NoError(GraphqlRetry(query, &response, func() bool { return response.DefaultSchemaConfig != nil }))
	s.NotNil(response.DefaultSchemaConfig)
	s.NotEmpty(response.DefaultSchemaConfig.ConfigVals)

	ucv := strings.ReplaceAll(updateConfigValues, "\n", "\\n")
	var resp struct{ CreateHelmRelease bool }
	mutation = s.createHelmReleaseMutation(testOrgBannerEdgeID, testClusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "")
	ResolverClient.MustPost(mutation, &resp)
	s.True(resp.CreateHelmRelease)

	helmEdgeID, err := s.getHelmEdgeIDByNameAndID(s.ctx, name, nil, &testOrgBannerEdgeID)
	s.NoError(err)

	// Case 1: Update workload using helmEdgeID
	var updatedResponse struct{ UpdateHelmRelease bool }
	mutation = updateHelmReleaseMutation(helmEdgeID, "", "", testHelmVersion, ucv, secrets, testLabelEdgeIDs)
	ResolverClient.MustPost(mutation, &updatedResponse)
	s.True(updatedResponse.UpdateHelmRelease)

	var res struct{ HelmWorkload *model.HelmWorkload }
	query = helmWorkloadQuery(testClusterEdgeID, helmEdgeID)
	ResolverClient.MustPost(query, &res)
	s.NotNil(res.HelmWorkload)
	s.Equal(testHelmVersion, res.HelmWorkload.HelmChartVersion)
	s.NotNil(res.HelmWorkload.ConfigValues)
	s.NotEqual("", *res.HelmWorkload.ConfigValues)
	s.NotNil(res.HelmWorkload.InstalledBy)
	s.Equal("acct:emerald-edge-dev@testing", *res.HelmWorkload.InstalledBy)
	s.NotEmpty(res.HelmWorkload.Secrets)
	s.NotEmpty(res.HelmWorkload.Labels)

	// Case 2: Update workload using helmReleaseName and clusterEdgeID
	mutation = updateHelmReleaseMutation("", testClusterEdgeID, name, "", ucv, []string{}, []string{})
	ResolverClient.MustPost(mutation, &updatedResponse)
	s.True(updatedResponse.UpdateHelmRelease)

	query = helmWorkloadQuery(testClusterEdgeID, helmEdgeID)
	ResolverClient.MustPost(query, &res)
	s.NotNil(res.HelmWorkload)
	s.Equal(testHelmVersion, res.HelmWorkload.HelmChartVersion)
	s.NotNil(res.HelmWorkload.ConfigValues)
	s.NotEqual("", *res.HelmWorkload.ConfigValues)
	s.NotNil(res.HelmWorkload.InstalledBy)
	s.Equal("acct:emerald-edge-dev@testing", *res.HelmWorkload.InstalledBy)
	s.Empty(res.HelmWorkload.Secrets)
	s.Empty(res.HelmWorkload.Labels)
}

func (s Suite) TestHelmChartVersion() {
	integration.SkipIf(s.Framework)
	helmURL := testHelmRepoURL
	if !integration.IsIntegrationTest() {
		srv := s.createHelmRepoTestServer()
		helmURL = srv.URL
	}

	chartSecret := fmt.Sprintf("version-chart-secret-%s", s.testTime)
	var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool }
	mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{
		{Key: "username", Value: "jd250001"},
		{Key: "password", Value: "password1"},
		{Key: "helmUrl", Value: helmURL},
	})
	ResolverClient.MustPost(mutation, &secretResponse)
	s.True(secretResponse.CreateOrUpdateSecretManagerSecret)

	var resp struct{ HelmChartVersion *model.HelmChartResponse }
	query := helmChartVersionQuery(testHelmChart, chartSecret, testOrgBannerEdgeID)
	s.NoError(GraphqlRetry(query, &resp, func() bool { return resp.HelmChartVersion != nil }))
	s.NotNil(resp.HelmChartVersion)
	s.NotEmpty(resp.HelmChartVersion.Name)
	s.NotEmpty(resp.HelmChartVersion.Versions)
}

func (s Suite) TestDefaultSchemaConfig() { //nolint: dupl
	integration.SkipIf(s.Framework)
	helmURL := testHelmRepoURL
	if !integration.IsIntegrationTest() {
		srv := s.createHelmRepoTestServer()
		helmURL = srv.URL
	}

	chartSecret := fmt.Sprintf("config-chart-secret-%s", s.testTime)
	var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool }
	mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{
		{Key: "username", Value: "jd250001"},
		{Key: "password", Value: "password1"},
		{Key: "helmUrl", Value: helmURL},
	})
	ResolverClient.MustPost(mutation, &secretResponse)
	s.True(secretResponse.CreateOrUpdateSecretManagerSecret)

	var resp struct{ DefaultSchemaConfig *model.HelmConfig }
	query := defaultSchemaConfigQuery(chartSecret, testOrgBannerEdgeID, testHelmChart, testHelmVersion)
	s.NoError(GraphqlRetry(query, &resp, func() bool { return resp.DefaultSchemaConfig != nil }))
	s.NotNil(resp.DefaultSchemaConfig)
	s.NotEmpty(resp.DefaultSchemaConfig.ConfigVals)
}

func (s Suite) TestHelmRelease() {
	integration.SkipIf(s.Framework)
	if !integration.IsIntegrationTest() {
		srv := s.createHelmRepoTestServer()
		s.NoError(createTestHelmRepoSecret(testOrgBannerEdgeID, testHelmSecret, srv.URL))
	}

	namespace := fmt.Sprintf("helmrelease-%s", s.testTime)
	name := fmt.Sprintf("helmreleases-%s", s.testTime)

	var resp struct{ CreateHelmRelease bool }
	mutation := s.createHelmReleaseMutation(testOrgBannerEdgeID, testClusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "")
	ResolverClient.MustPost(mutation, &resp)

	s.True(resp.CreateHelmRelease)
}

func (s Suite) TestHelmReleasesStatus() {
	integration.SkipIf(s.Framework)
	helmURL := testHelmRepoURL
	if !integration.IsIntegrationTest() {
		srv := s.createHelmRepoTestServer()
		helmURL = srv.URL
	}

	chartSecret := fmt.Sprintf("chart-secret-%s", s.testTime)

	var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } //nolint: dupl
	mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{
		{Key: "helm_repo_name", Value: testHelmRepo},
		{Key: "helmUrl", Value: helmURL},
	})
	ResolverClient.MustPost(mutation, &secretResponse)
	s.True(secretResponse.CreateOrUpdateSecretManagerSecret)

	var secretManagerQuery struct{ SecretManagerSecret model.SecretManagerResponse }
	secretQuery := getSecretManagerSecret(chartSecret, testOrgBannerEdgeID, true)
	ResolverClient.MustPost(secretQuery, &secretManagerQuery)
	s.Equal(len(secretManagerQuery.SecretManagerSecret.Values), 2)

	var response struct{ HelmReleasesStatus []*model.HelmReleaseStatus }
	query := helmReleasesStatusQuery(testClusterEdgeID)
	s.NoError(GraphqlRetry(query, &response, func() bool { return len(response.HelmReleasesStatus) > 0 }))
	for _, helmReleasesStatus := range response.HelmReleasesStatus {
		s.NotEmpty(helmReleasesStatus.Name)
		s.Empty(helmReleasesStatus.LastActionTime)
		s.Empty(helmReleasesStatus.VersionInstalled)
		s.NotEmpty(helmReleasesStatus.VersionRequested)
		s.Empty(helmReleasesStatus.InstallCondition)
		s.Empty(helmReleasesStatus.ReadyCondition)
		s.Empty(helmReleasesStatus.ConfigValues)
	}
}

func (s Suite) TestHelmCharts() {
	integration.SkipIf(s.Framework)
	helmURL := testHelmRepoURL
	if !integration.IsIntegrationTest() {
		srv := s.createHelmRepoTestServer()
		helmURL = srv.URL
	}

	chartSecret := fmt.Sprintf("helm-chart-secret-%s", s.testTime)
	var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } //nolint: dupl
	mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{
		{Key: "username", Value: "jd250001"},
		{Key: "password", Value: "password1"},
		{Key: "helmUrl", Value: helmURL},
	})
	ResolverClient.MustPost(mutation, &secretResponse)
	s.True(secretResponse.CreateOrUpdateSecretManagerSecret)

	var resp struct{ HelmCharts []*model.HelmChart }
	query := helmChartsQuery(chartSecret, testOrgBannerEdgeID)
	s.NoError(GraphqlRetry(query, &resp, func() bool { return len(resp.HelmCharts) > 0 }))
	s.Len(resp.HelmCharts, 2)
	s.NotEmpty(resp.HelmCharts[0].Name)
	s.NotEmpty(resp.HelmCharts[1].Name)
}

func (s Suite) TestHelmRepositoryInfo() { //nolint: dupl
	integration.SkipIf(s.Framework)
	helmURL := testHelmRepoURL
	if !integration.IsIntegrationTest() {
		srv := s.createHelmRepoTestServer()
		helmURL = srv.URL
	}

	chartSecret := fmt.Sprintf("helmrepo-info-secret-%s", s.testTime)
	var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool }
	mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{
		{Key: "username", Value: "jd250001"},
		{Key: "password", Value: "password1"},
		{Key: "helmUrl", Value: helmURL},
	})
	ResolverClient.MustPost(mutation, &secretResponse)
	s.True(secretResponse.CreateOrUpdateSecretManagerSecret)

	var resp struct{ HelmRepositoryInfo *model.HelmRepositoryInfo }
	query := helmRepositoryInfoQuery(chartSecret, testOrgBannerEdgeID, testHelmChart, testHelmVersion)
	s.NoError(GraphqlRetry(query, &resp, func() bool { return resp.HelmRepositoryInfo != nil }))
	s.NotNil(resp.HelmRepositoryInfo)
	s.NotEmpty(resp.HelmRepositoryInfo.Readme)
}

func (s Suite) TestGetHelmWorkloads() {
	integration.SkipIf(s.Framework)

	srv := s.createHelmRepoTestServer()
	helmURL := srv.URL

	var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } //nolint: dupl
	mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, "test-helm-workload", "edge-helm", "helm-repository", "tenant", []model.KeyValues{
		{Key: "username", Value: "jd250001"},
		{Key: "password", Value: "password1"},
		{Key: "helmUrl", Value: helmURL},
	})

	ResolverClient.MustPost(mutation, &secretResponse)
	s.True(secretResponse.CreateOrUpdateSecretManagerSecret)

	var response struct{ HelmWorkloads []*model.HelmWorkload }
	query := helmWorkloadsQuery(testClusterEdgeID, "")

	s.NoError(GraphqlRetry(query, &response, func() bool { return len(response.HelmWorkloads) > 0 }))

	responseMap := make(map[string]bool, 0)
	for i := 0; i < len(response.HelmWorkloads); i++ {
		if responseMap[response.HelmWorkloads[i].HelmEdgeID] {
			s.Fail(fmt.Sprintf("duplicate helm release returned: %s", response.HelmWorkloads[i].Name))
		} else {
			responseMap[response.HelmWorkloads[i].HelmEdgeID] = true
		}
	}

	s.NotNil(response.HelmWorkloads)
	s.GreaterOrEqual(len(response.HelmWorkloads), 1)
	s.Equal(testClusterEdgeID, response.HelmWorkloads[0].ClusterEdgeID)
	s.NotEmpty(response.HelmWorkloads[0].Name)
	for _, helmWorkloadVal := range response.HelmWorkloads {
		s.NotEmpty(helmWorkloadVal.HelmEdgeID)
		s.NotEmpty(helmWorkloadVal.HelmChart)
		s.NotEmpty(helmWorkloadVal.HelmChartVersion)
		s.NotEmpty(helmWorkloadVal.HelmRepoSecret)
		s.NotEmpty(helmWorkloadVal.HelmRepository)
		s.NotEmpty(helmWorkloadVal.Namespace)
		s.NotEmpty(helmWorkloadVal.InstalledBy)
		s.Empty(*helmWorkloadVal.UpdateAvailable)
		s.NotEmpty(helmWorkloadVal.UpdatedAt)
		s.NotEmpty(helmWorkloadVal.CreatedAt)
		s.Equal(helmWorkloadVal.Labels, []*model.Label(nil))
	}

	// Test getting workloads by bannerEdgeID and verify that deployed workloads
	// have a clusterEdgeID and undeployed workloads don't.
	undeployedHelmEdgeID := "930c89f4-060b-456e-a779-7cacb50c3bec"
	deployedHelmEdgeID := testHelmEdgeID
	query = helmWorkloadsQuery("", testOrgBannerEdgeID)
	s.NoError(ResolverClient.Post(query, &response))
	s.NotEmpty(response.HelmWorkloads)

	for _, workload := range response.HelmWorkloads {
		if workload.HelmEdgeID == undeployedHelmEdgeID {
			s.Empty(workload.ClusterEdgeID)
		} else if workload.HelmEdgeID == deployedHelmEdgeID {
			s.Equal(testClusterEdgeID, workload.ClusterEdgeID)
		}
		s.Equal(testOrgBannerEdgeID, workload.BannerEdgeID)
		s.NotEmpty(workload.Name)
		s.NotEmpty(workload.HelmEdgeID)
		s.NotEmpty(workload.HelmChart)
		s.NotEmpty(workload.HelmChartVersion)
		s.NotEmpty(workload.HelmRepoSecret)
		s.NotEmpty(workload.HelmRepository)
		s.NotEmpty(workload.Namespace)
		s.NotEmpty(workload.InstalledBy)
		s.Empty(*workload.UpdateAvailable)
		s.NotEmpty(workload.UpdatedAt)
		s.NotEmpty(workload.CreatedAt)
		s.Equal(workload.Labels, []*model.Label(nil))
	}
}

func (s Suite) TestGetHelmWorkload() {
	integration.SkipIf(s.Framework)

	srv := s.createHelmRepoTestServer()
	helmURL := srv.URL

	var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } //nolint: dupl
	mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, "test-helm-workload", "edge-helm", "helm-repository", "tenant", []model.KeyValues{
		{Key: "username", Value: "jd250001"},
		{Key: "password", Value: "password1"},
		{Key: "helmUrl", Value: helmURL},
	})

	s.NoError(ResolverClient.Post(mutation, &secretResponse))
	s.True(secretResponse.CreateOrUpdateSecretManagerSecret)

	var response struct{ HelmWorkload *model.HelmWorkload }
	query := helmWorkloadQuery(testClusterEdgeID, testHelmEdgeID)
	s.NoError(ResolverClient.Post(query, &response))

	s.NotNil(response.HelmWorkload)
	s.NotEmpty(response.HelmWorkload.Name)
	s.Equal(response.HelmWorkload.Name, "test-helm-workload")
	s.NotEmpty(response.HelmWorkload.HelmEdgeID)
	s.NotEmpty(response.HelmWorkload.HelmChart)
	s.Equal(testOrgBannerEdgeID, response.HelmWorkload.BannerEdgeID)
	s.Equal(testClusterEdgeID, response.HelmWorkload.ClusterEdgeID)
	s.NotEmpty(response.HelmWorkload.HelmChartVersion)
	s.NotEmpty(response.HelmWorkload.HelmRepoSecret)
	s.NotEmpty(response.HelmWorkload.HelmRepository)
	s.NotEmpty(response.HelmWorkload.Namespace)
	s.NotEmpty(response.HelmWorkload.InstalledBy)
	s.Empty(*response.HelmWorkload.UpdateAvailable)
	s.NotEmpty(response.HelmWorkload.UpdatedAt)
	s.NotEmpty(response.HelmWorkload.CreatedAt)
	s.Equal(response.HelmWorkload.UpgradeableVersions, []*model.HelmVersion(nil))
	s.Equal(response.HelmWorkload.DowngradeableVersions, []*model.HelmVersion(nil))
	s.Equal(len(response.HelmWorkload.Secrets), 2)
	s.NotEmpty(response.HelmWorkload.Labels)
}

func (s Suite) TestAddWorkloadLabels() {
	integration.SkipIf(s.Framework)

	labelIDList := []string{"ca18bfab-091b-4a7f-9db6-011d2603b949", "bbb5a40b-65d1-4092-ab5f-e35d7c2482db"}
	invalidabelIDList := []string{"ca18bfabCHIPPY2603b949", "442SCOOBYDOO458b7536"}

	// Case 1: Adding valid label
	var response1 struct {
		AddWorkloadLabels bool
	}
	mutation1 := addWorkloadLabelsMutation(testHelmEdgeID, labelIDList)
	ResolverClient.MustPost(mutation1, &response1)
	s.NotNil(response1)
	s.Equal(true, response1.AddWorkloadLabels)

	// Case 2: Adding invalid label
	var response2 struct {
		AddWorkloadLabels bool
	}
	mutation2 := addWorkloadLabelsMutation(testHelmEdgeID, invalidabelIDList)
	err2 := ResolverClient.Post(mutation2, &response2)
	s.Equal(false, response2.AddWorkloadLabels)
	s.Contains(err2.Error(), "[{\"message\":\"Invalid input syntax for type\",\"extensions\":{\"additional\":{\"errorType\":\"EDGE_SQL_STATE\",\"severity\":\"\",\"statusCode\":\"Unknown\"}")
}

func (s Suite) TestDeleteWorkloadLabels() {
	integration.SkipIf(s.Framework)

	helmEdgeID := "be8536ff-d463-4aff-8fa9-fe81fec1ddc2"
	labelEdgeID := "aac1f183-50bc-453f-a822-9ab11aa70916"
	invalidLabel := "jibberish"
	deleteLabel := "bbb5a40b-65d1-4092-ab5f-e35d7c2482dc"

	workloadLabelInput := model.WorkloadLabelInput{
		HelmEdgeID:  helmEdgeID,
		LabelEdgeID: labelEdgeID,
	}

	// Case 1: Deleting valid label
	var response1 struct {
		DeleteWorkloadLabel bool
	}
	mutation1 := deleteWorkloadLabelMutation(workloadLabelInput.HelmEdgeID, workloadLabelInput.LabelEdgeID)
	ResolverClient.MustPost(mutation1, &response1)
	s.NotNil(response1)

	// Case 2: Deleting invalid label
	var response2 struct {
		DeleteWorkloadLabel bool
	}
	mutation2 := deleteWorkloadLabelMutation(workloadLabelInput.HelmEdgeID, invalidLabel)
	err2 := ResolverClient.Post(mutation2, &response2)
	s.Equal(false, response2.DeleteWorkloadLabel)
	s.Contains(err2.Error(), "[{\"message\":\"failed to delete labels from workload\",\"path\":[\"deleteWorkloadLabel\"]}]")

	// Case 3: Deleting empty label
	var response3 struct {
		DeleteWorkloadLabel bool
	}
	mutation3 := deleteWorkloadLabelMutation(workloadLabelInput.HelmEdgeID, "")
	ResolverClient.MustPost(mutation3, &response3)
	s.NotNil(response3)
	s.Equal(true, response3.DeleteWorkloadLabel)

	// Case 4: Deleting with label edge id
	var response4 struct {
		DeleteWorkloadLabel bool
	}
	mutation4 := deleteWorkloadLabelMutation("", deleteLabel)
	ResolverClient.MustPost(mutation4, &response4)
	s.NotNil(response4)
	s.Equal(true, response4.DeleteWorkloadLabel)
}

func (s Suite) createHelmRepoTestServer() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var err error
		w.WriteHeader(200)
		if strings.HasSuffix(r.URL.String(), "/README.md") {
			_, err = w.Write([]byte(readme))
		} else if strings.HasSuffix(r.URL.String(), "/values.schema.json") {
			_, err = w.Write([]byte(schemaData))
		} else if strings.HasSuffix(r.URL.String(), ".tgz") {
			_, err = w.Write([]byte(zippedChart))
			w.Header().Set("Content-Type", "application/gzip")
			w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.tgz\"", "alpine-0.2.0.tgz"))
		} else {
			_, err = w.Write([]byte(getTestHelmRepoIndex(helmCharts, r.Host)))
		}
		s.NoError(err)
	}))
}

func getTestHelmRepoIndex(helmCharts, url string) string {
	return fmt.Sprintf(helmCharts, url, url)
}

func createTestHelmRepoSecret(bannerEdgeID, name, helmURL string) error {
	var createResponse struct{ CreateOrUpdateSecretManagerSecret bool }
	mutation := createOrUpdateSecretManagerSecretMutation(bannerEdgeID, name, "edge-helm", "helm-repository", "tenant", []model.KeyValues{
		{Key: "helm_repo_name", Value: name},
		{Key: "helmUrl", Value: helmURL},
	})
	ResolverClient.MustPost(mutation, &createResponse)
	mutation = createOrUpdateBannerHelmRepositoryMutation(bannerEdgeID, name, testHelmRepoURL, name)
	var createUpdateRepositoryResponse struct{ CreateOrUpdateBannerHelmRepository bool }
	ResolverClient.MustPost(mutation, &createUpdateRepositoryResponse)
	if !createUpdateRepositoryResponse.CreateOrUpdateBannerHelmRepository {
		return fmt.Errorf("unable to create or update HelmRepository")
	}

	return nil
}

func (s *Suite) createHelmReleaseMutation(bannerEdgeID, clusterEdgeID, name, namespace, secret, helmRepository, helmChart, version, configValues string, helmEdgeID string, secrets ...string) string { //nolint: unparam
	payloadArgs := []graphb.Argument{
		graphb.ArgumentString("name", name),
		graphb.ArgumentString("secret", secret),
		graphb.ArgumentString("helmRepository", helmRepository),
		graphb.ArgumentString("helmChart", helmChart),
		graphb.ArgumentString("version", version),
		graphb.ArgumentString("configValues", ""),
	}

	if bannerEdgeID != "" {
		payloadArgs = append(payloadArgs, graphb.ArgumentString("bannerEdgeId", bannerEdgeID))
	}
	if clusterEdgeID != "" {
		payloadArgs = append(payloadArgs, graphb.ArgumentString("clusterEdgeId", clusterEdgeID))
	}
	if len(secrets) > 0 {
		payloadArgs = append(payloadArgs, graphb.ArgumentStringSlice("secrets", secrets...))
	}
	if namespace != "" {
		payloadArgs = append(payloadArgs, graphb.ArgumentString("namespace", namespace))
	}
	if configValues != "" {
		payloadArgs = append(payloadArgs, graphb.ArgumentString("configValues", configValues))
	}
	if !integration.IsIntegrationTest() {
		var installationType model.WorkloadInstallationType
		obj, err := mapper.ToCreateHelmRelease(name, helmRepository, helmChart, version, secret, namespace, nil, installationType, nil, "0.21", helmEdgeID)
		s.NoError(err)
		s.NoError(createHelmReleaseResources(obj.(*helmApi.HelmRelease)))
	}
	return MustParse(graphb.Query{
		Type: graphb.TypeMutation,
		Fields: []*graphb.Field{
			{
				Name:      "createHelmRelease",
				Arguments: []graphb.Argument{graphb.ArgumentCustomType("payload", payloadArgs...)},
			},
		},
	})
}

func createOrUpdateBannerHelmRepositoryMutation(bannerEdgeID, name, url, secret string) string {
	args := []graphb.Argument{
		graphb.ArgumentString("bannerEdgeId", bannerEdgeID),
		graphb.ArgumentString("name", name),
		graphb.ArgumentString("url", url),
	}
	if secret != "" {
		args = append(args, graphb.ArgumentString("secret", secret))
	}
	return MustParse(graphb.Query{
		Type: graphb.TypeMutation,
		Fields: []*graphb.Field{
			{
				Name:      "createOrUpdateBannerHelmRepository",
				Arguments: args,
			},
		},
	})
}

func helmChartsQuery(secretName, bannerEdgeID string) string {
	return MustParse(graphb.Query{
		Type: graphb.TypeQuery,
		Fields: []*graphb.Field{
			{
				Name: "helmCharts",
				Arguments: []graphb.Argument{
					graphb.ArgumentString("secretName", secretName),
					graphb.ArgumentString("bannerEdgeId", bannerEdgeID),
				},
				Fields: graphb.Fields("name", "description", "version", "appVersion", "icon", "keywords", "sources", "urls", "created"),
			},
		},
	})
}

func defaultSchemaConfigQuery(secretName, bannerEdgeID, chartName, chartVersion string) string {
	paramsArgs := []graphb.Argument{
		graphb.ArgumentString("bannerEdgeId", bannerEdgeID),
		graphb.ArgumentString("secretName", secretName),
		graphb.ArgumentString("chartName", chartName),
		graphb.ArgumentString("chartVersion", chartVersion),
	}
	return MustParse(graphb.Query{
		Type: graphb.TypeQuery,
		Fields: []*graphb.Field{
			{
				Name:      "defaultSchemaConfig",
				Arguments: []graphb.Argument{graphb.ArgumentCustomType("params", paramsArgs...)},
				Fields:    graphb.Fields("configVals", "configSchema"),
			},
		},
	})
}

func helmWorkloadsQuery(clusterEdgeID, bannerEdgeID string) string {
	return MustParse(graphb.Query{
		Type: graphb.TypeQuery,
		Fields: []*graphb.Field{
			{
				Name: "helmWorkloads",
				Arguments: []graphb.Argument{
					graphb.ArgumentString("clusterEdgeId", clusterEdgeID),
					graphb.ArgumentString("bannerEdgeId", bannerEdgeID),
				},
				Fields: []*graphb.Field{
					graphb.NewField("helmChart"),
					graphb.NewField("helmChartVersion"),
					graphb.NewField("helmEdgeID"),
					graphb.NewField("clusterEdgeID"),
					graphb.NewField("bannerEdgeID"),
					graphb.NewField("helmRepoSecret"),
					graphb.NewField("helmRepository"),
					graphb.NewField("installedBy"),
					graphb.NewField("name"),
					graphb.NewField("namespace"),
					graphb.NewField("installationType"),
					graphb.NewField("updateAvailable"),
					graphb.NewField("configValues"),
					graphb.NewField("createdAt"),
					graphb.NewField("updatedAt"),
					{
						Name:   "secrets",
						Fields: graphb.Fields("createdAt", "name", "secretEdgeID", "updatedAt"),
					},
					{
						Name:   "upgradeableVersions",
						Fields: graphb.Fields("version"),
					},
					{
						Name:   "downgradeableVersions",
						Fields: graphb.Fields("version"),
					},
				},
			},
		},
	})
}

func helmWorkloadQuery(clusterEdgeID string, helmEdgeID string) string {
	return MustParse(graphb.Query{
		Type: graphb.TypeQuery,
		Fields: []*graphb.Field{
			{
				Name: "helmWorkload",
				Arguments: []graphb.Argument{
					graphb.ArgumentString("clusterEdgeId", clusterEdgeID),
					graphb.ArgumentString("helmEdgeId", helmEdgeID),
				},
				Fields: []*graphb.Field{
					graphb.NewField("bannerEdgeID"),
					graphb.NewField("clusterEdgeID"),
					graphb.NewField("helmChart"),
					graphb.NewField("helmChartVersion"),
					graphb.NewField("helmEdgeID"),
					graphb.NewField("helmRepoSecret"),
					graphb.NewField("helmRepository"),
					graphb.NewField("installedBy"),
					graphb.NewField("name"),
					graphb.NewField("namespace"),
					graphb.NewField("installationType"),
					graphb.NewField("updateAvailable"),
					graphb.NewField("configValues"),
					graphb.NewField("createdAt"),
					graphb.NewField("updatedAt"),
					{
						Name:   "secrets",
						Fields: graphb.Fields("createdAt", "name", "secretEdgeID", "updatedAt"),
					},
					{
						Name:   "upgradeableVersions",
						Fields: graphb.Fields("version"),
					},
					{
						Name:   "downgradeableVersions",
						Fields: graphb.Fields("version"),
					},
					{
						Name:   "labels",
						Fields: graphb.Fields("labelEdgeId", "key", "color", "visible", "editable", "unique", "bannerEdgeId", "description", "type"),
					},
				},
			},
		},
	})
}

func helmRepositoryInfoQuery(secretName, bannerEdgeID, chartName, chartVersion string) string {
	paramsArgs := []graphb.Argument{
		graphb.ArgumentString("bannerEdgeId", bannerEdgeID),
		graphb.ArgumentString("secretName", secretName),
		graphb.ArgumentString("chartName", chartName),
		graphb.ArgumentString("chartVersion", chartVersion),
	}
	return MustParse(graphb.Query{
		Type: graphb.TypeQuery,
		Fields: []*graphb.Field{
			{
				Name:      "helmRepositoryInfo",
				Arguments: []graphb.Argument{graphb.ArgumentCustomType("params", paramsArgs...)},
				Fields:    graphb.Fields("readme", "metadata"),
			},
		},
	})
}

func helmReleasesStatusQuery(clusterEdgeID string) string {
	return MustParse(graphb.Query{
		Type: graphb.TypeQuery,
		Fields: []*graphb.Field{
			{
				Name: "helmReleasesStatus",
				Arguments: []graphb.Argument{
					graphb.ArgumentString("clusterEdgeId", clusterEdgeID),
				},
				Fields: []*graphb.Field{
					graphb.NewField("configValues"),
					graphb.NewField("lastActionTime"),
					graphb.NewField("name"),
					graphb.NewField("statusType"),
					graphb.NewField("versionInstalled"),
					graphb.NewField("versionRequested"),
					{
						Name:   "installCondition",
						Fields: graphb.Fields("installed", "lastTransitionTime", "ready", "message", "reason", "status", "type"),
					},
					{
						Name:   "readyCondition",
						Fields: graphb.Fields("installed", "lastTransitionTime", "message", "ready", "reason", "status", "type"),
					},
				},
			},
		},
	})
}

func helmChartVersionQuery(name, secretName, bannerEdgeID string) string {
	return MustParse(graphb.Query{
		Type: graphb.TypeQuery,
		Fields: []*graphb.Field{
			{
				Name: "helmChartVersion",
				Arguments: []graphb.Argument{
					graphb.ArgumentString("name", name),
					graphb.ArgumentString("secretName", secretName),
					graphb.ArgumentString("bannerEdgeId", bannerEdgeID),
				},
				Fields: graphb.Fields("name", "versions"),
			},
		},
	})
}

func updateHelmReleaseMutation(helmEdgeID, clusterEdgeID, helmReleaseName, version, configValues string, secrets, labelEdgeIDs []string) string {
	args := []graphb.Argument{
		graphb.ArgumentString("helmEdgeId", helmEdgeID),
		graphb.ArgumentString("clusterEdgeId", clusterEdgeID),
		graphb.ArgumentString("helmReleaseName", helmReleaseName),
	}
	if version != "" {
		args = append(args, graphb.ArgumentString("version", version))
	}
	if configValues != "" {
		args = append(args, graphb.ArgumentString("configValues", configValues))
	}
	if secrets != nil {
		args = append(args, graphb.ArgumentStringSlice("secrets", secrets...))
	}
	if labelEdgeIDs != nil {
		args = append(args, graphb.ArgumentStringSlice("labelEdgeIds", labelEdgeIDs...))
	}

	return MustParse(graphb.Query{
		Type: graphb.TypeMutation,
		Fields: []*graphb.Field{
			{
				Name:      "updateHelmRelease",
				Arguments: args,
			},
		},
	})
}

func deleteHelmReleaseMutation(helmEdgeID, clusterEdgeID, name string) string {
	var args = []graphb.Argument{
		graphb.ArgumentString("helmEdgeId", helmEdgeID),
		graphb.ArgumentString("clusterEdgeId", clusterEdgeID),
		graphb.ArgumentString("name", name),
	}
	return MustParse(graphb.Query{
		Type: graphb.TypeMutation,
		Fields: []*graphb.Field{
			{
				Name:      "deleteHelmRelease",
				Arguments: args,
			},
		},
	})
}

// createHelmReleaseResources NOTE: any time we create HelmRelease we should create the resources
func createHelmReleaseResources(hr *helmApi.HelmRelease) error {
	namespace := hr.Spec.TargetNamespace
	name := hr.Name
	httpPort := intstr.FromInt(8000)
	dep := mapper.GetTestWorkloadDeployment(httpPort)
	ss := mapper.GetTestWorkloadStatefulSet(httpPort)
	ds := mapper.GetTestWorkloadDaemonSet(httpPort)

	releaseName := fmt.Sprintf("%s-%s", namespace, name)
	dep.Namespace = namespace
	dep.Name = name
	dep.Annotations["meta.helm.sh/release-name"] = releaseName

	ss.Namespace = namespace
	ss.Name = name
	ss.Annotations["meta.helm.sh/release-name"] = releaseName

	ds.Namespace = namespace
	ds.Name = name
	ds.Annotations["meta.helm.sh/release-name"] = releaseName

	if err := runtimeClient.Create(context.Background(), dep); err != nil {
		return err
	}
	if err := runtimeClient.Create(context.Background(), ss); err != nil {
		return err
	}
	return runtimeClient.Create(context.Background(), ds)
}

func addWorkloadLabelsMutation(helmEdgeID string, labelEdgeIDs []string) string {
	args := []graphb.Argument{
		graphb.ArgumentString("helmEdgeId", helmEdgeID),
	}
	if labelEdgeIDs != nil {
		args = append(args, graphb.ArgumentStringSlice("labelEdgeIds", labelEdgeIDs...))
	}
	return MustParse(graphb.Query{
		Type: graphb.TypeMutation,
		Fields: []*graphb.Field{
			{
				Name:      "addWorkloadLabels",
				Arguments: args,
			},
		},
	})
}

func deleteWorkloadLabelMutation(helmEdgeID string, labelEdgeID string) string {
	workloadLabelParams := graphb.ArgumentCustomTypeSliceElem(
		graphb.ArgumentString("helmEdgeId", helmEdgeID),
		graphb.ArgumentString("labelEdgeId", labelEdgeID),
	)
	args := []graphb.Argument{
		graphb.ArgumentCustomType("workloadLabelParameters", workloadLabelParams...),
	}

	return MustParse(graphb.Query{
		Type: graphb.TypeMutation,
		Fields: []*graphb.Field{
			{
				Name:      "deleteWorkloadLabel",
				Arguments: args,
			},
		},
	})
}

// Note: this function does not reliably fetch a workload's helmEdgeID if there are multiple
// workloads with the same name under the same banner or cluster. Use with caution.
func (s Suite) getHelmEdgeIDByNameAndID(ctx context.Context, helmReleaseName string, clusterEdgeID, bannerEdgeID *string) (string, error) {
	var (
		helmEdgeID string
		row        *sql.Row
	)

	if !utils.IsNullOrEmpty(bannerEdgeID) {
		row = s.DB.QueryRowContext(ctx, sqlquery.GetHelmEdgeIDByNameAndBannerEdgeID, helmReleaseName, bannerEdgeID)
	} else if !utils.IsNullOrEmpty(clusterEdgeID) {
		row = s.DB.QueryRowContext(ctx, sqlquery.GetHelmEdgeIDByNameAndClusterEdgeID, helmReleaseName, clusterEdgeID)
	} else {
		return "", apierror.New("please provide clusterEdgeId or bannerEdgeId")
	}

	if err := row.Scan(&helmEdgeID); err != nil {
		return "", fmt.Errorf("failed to get helmEdgeID for workload: %w", err)
	}
	return helmEdgeID, nil
}