package edgestacks

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strconv"
	"testing"
	"time"

	portainer "github.com/portainer/portainer/api"
	"github.com/portainer/portainer/api/apikey"
	"github.com/portainer/portainer/api/dataservices"
	"github.com/portainer/portainer/api/datastore"
	"github.com/portainer/portainer/api/filesystem"
	"github.com/portainer/portainer/api/http/security"
	"github.com/portainer/portainer/api/internal/edge/edgestacks"
	"github.com/portainer/portainer/api/internal/testhelpers"
	"github.com/portainer/portainer/api/jwt"

	"github.com/pkg/errors"
)

// Helpers
func setupHandler(t *testing.T) (*Handler, string, func()) {
	t.Helper()

	_, store, storeTeardown := datastore.MustNewTestStore(t, true, true)

	jwtService, err := jwt.NewService("1h", store)
	if err != nil {
		storeTeardown()
		t.Fatal(err)
	}

	user := &portainer.User{ID: 2, Username: "admin", Role: portainer.AdministratorRole}
	err = store.User().Create(user)
	if err != nil {
		storeTeardown()
		t.Fatal(err)
	}

	apiKeyService := apikey.NewAPIKeyService(store.APIKeyRepository(), store.User())
	rawAPIKey, _, err := apiKeyService.GenerateApiKey(*user, "test")
	if err != nil {
		storeTeardown()
		t.Fatal(err)
	}

	edgeStacksService := edgestacks.NewService(store)

	handler := NewHandler(
		security.NewRequestBouncer(store, jwtService, apiKeyService),
		store,
		edgeStacksService,
	)

	tmpDir := t.TempDir()

	fs, err := filesystem.NewService(tmpDir, "")
	if err != nil {
		storeTeardown()
		t.Fatal(err)
	}
	handler.FileService = fs

	settings, err := handler.DataStore.Settings().Settings()
	if err != nil {
		t.Fatal(err)
	}
	settings.EnableEdgeComputeFeatures = true

	err = handler.DataStore.Settings().UpdateSettings(settings)
	if err != nil {
		t.Fatal(err)
	}

	handler.GitService = testhelpers.NewGitService(errors.New("Clone error"), "git-service-id")

	return handler, rawAPIKey, storeTeardown
}

func createEndpointWithId(t *testing.T, store dataservices.DataStore, endpointID portainer.EndpointID) portainer.Endpoint {
	t.Helper()

	endpoint := portainer.Endpoint{
		ID:              endpointID,
		Name:            "test-endpoint-" + strconv.Itoa(int(endpointID)),
		Type:            portainer.EdgeAgentOnDockerEnvironment,
		URL:             "https://portainer.io:9443",
		EdgeID:          "edge-id",
		LastCheckInDate: time.Now().Unix(),
	}

	err := store.Endpoint().Create(&endpoint)
	if err != nil {
		t.Fatal(err)
	}

	return endpoint
}

func createEndpoint(t *testing.T, store dataservices.DataStore) portainer.Endpoint {
	return createEndpointWithId(t, store, 5)
}

func createEdgeStack(t *testing.T, store dataservices.DataStore, endpointID portainer.EndpointID) portainer.EdgeStack {
	t.Helper()

	edgeGroup := portainer.EdgeGroup{
		ID:           1,
		Name:         "EdgeGroup 1",
		Dynamic:      false,
		TagIDs:       nil,
		Endpoints:    []portainer.EndpointID{endpointID},
		PartialMatch: false,
	}

	err := store.EdgeGroup().Create(&edgeGroup)
	if err != nil {
		t.Fatal(err)
	}

	edgeStackID := portainer.EdgeStackID(14)
	edgeStack := portainer.EdgeStack{
		ID:   edgeStackID,
		Name: "test-edge-stack-" + strconv.Itoa(int(edgeStackID)),
		Status: map[portainer.EndpointID]portainer.EdgeStackStatus{
			endpointID: {Details: portainer.EdgeStackStatusDetails{Ok: true}, Error: "", EndpointID: endpointID},
		},
		CreationDate:   time.Now().Unix(),
		EdgeGroups:     []portainer.EdgeGroupID{edgeGroup.ID},
		ProjectPath:    "/project/path",
		EntryPoint:     "entrypoint",
		Version:        237,
		ManifestPath:   "/manifest/path",
		DeploymentType: portainer.EdgeStackDeploymentKubernetes,
	}

	endpointRelation := portainer.EndpointRelation{
		EndpointID: endpointID,
		EdgeStacks: map[portainer.EdgeStackID]bool{
			edgeStack.ID: true,
		},
	}

	err = store.EdgeStack().Create(edgeStack.ID, &edgeStack)
	if err != nil {
		t.Fatal(err)
	}

	err = store.EndpointRelation().Create(&endpointRelation)
	if err != nil {
		t.Fatal(err)
	}

	return edgeStack
}

// Inspect
func TestInspectInvalidEdgeID(t *testing.T) {
	handler, rawAPIKey, teardown := setupHandler(t)
	defer teardown()

	cases := []struct {
		Name               string
		EdgeStackID        string
		ExpectedStatusCode int
	}{
		{"Invalid EdgeStackID", "x", 400},
		{"Non-existing EdgeStackID", "5", 404},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			req, err := http.NewRequest(http.MethodGet, "/edge_stacks/"+tc.EdgeStackID, nil)
			if err != nil {
				t.Fatal("request error:", err)
			}

			req.Header.Add("x-api-key", rawAPIKey)
			rec := httptest.NewRecorder()
			handler.ServeHTTP(rec, req)

			if rec.Code != tc.ExpectedStatusCode {
				t.Fatalf("expected a %d response, found: %d", tc.ExpectedStatusCode, rec.Code)
			}
		})
	}
}

// Create
func TestCreateAndInspect(t *testing.T) {
	handler, rawAPIKey, teardown := setupHandler(t)
	defer teardown()

	// Create Endpoint, EdgeGroup and EndpointRelation
	endpoint := createEndpoint(t, handler.DataStore)
	edgeGroup := portainer.EdgeGroup{
		ID:           1,
		Name:         "EdgeGroup 1",
		Dynamic:      false,
		TagIDs:       nil,
		Endpoints:    []portainer.EndpointID{endpoint.ID},
		PartialMatch: false,
	}

	err := handler.DataStore.EdgeGroup().Create(&edgeGroup)
	if err != nil {
		t.Fatal(err)
	}

	endpointRelation := portainer.EndpointRelation{
		EndpointID: endpoint.ID,
		EdgeStacks: map[portainer.EdgeStackID]bool{},
	}

	err = handler.DataStore.EndpointRelation().Create(&endpointRelation)
	if err != nil {
		t.Fatal(err)
	}

	payload := swarmStackFromFileContentPayload{
		Name:             "Test Stack",
		StackFileContent: "stack content",
		EdgeGroups:       []portainer.EdgeGroupID{1},
		DeploymentType:   portainer.EdgeStackDeploymentCompose,
	}

	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		t.Fatal("JSON marshal error:", err)
	}
	r := bytes.NewBuffer(jsonPayload)

	// Create EdgeStack
	req, err := http.NewRequest(http.MethodPost, "/edge_stacks?method=string", r)
	if err != nil {
		t.Fatal("request error:", err)
	}
	req.Header.Add("x-api-key", rawAPIKey)
	rec := httptest.NewRecorder()
	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
	}

	data := portainer.EdgeStack{}
	err = json.NewDecoder(rec.Body).Decode(&data)
	if err != nil {
		t.Fatal("error decoding response:", err)
	}

	// Inspect
	req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/edge_stacks/%d", data.ID), nil)
	if err != nil {
		t.Fatal("request error:", err)
	}

	req.Header.Add("x-api-key", rawAPIKey)
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
	}

	data = portainer.EdgeStack{}
	err = json.NewDecoder(rec.Body).Decode(&data)
	if err != nil {
		t.Fatal("error decoding response:", err)
	}

	if payload.Name != data.Name {
		t.Fatalf("expected EdgeStack Name %s, found %s", payload.Name, data.Name)
	}
}

func TestCreateWithInvalidPayload(t *testing.T) {
	handler, rawAPIKey, teardown := setupHandler(t)
	defer teardown()

	endpoint := createEndpoint(t, handler.DataStore)
	edgeStack := createEdgeStack(t, handler.DataStore, endpoint.ID)

	cases := []struct {
		Name               string
		Payload            interface{}
		QueryString        string
		ExpectedStatusCode int
	}{
		{
			Name:               "Invalid query string parameter",
			Payload:            swarmStackFromFileContentPayload{},
			QueryString:        "invalid=query-string",
			ExpectedStatusCode: 400,
		},
		{
			Name:               "Invalid creation method",
			Payload:            swarmStackFromFileContentPayload{},
			QueryString:        "method=invalid-creation-method",
			ExpectedStatusCode: 500,
		},
		{
			Name:               "Empty swarmStackFromFileContentPayload with string method",
			Payload:            swarmStackFromFileContentPayload{},
			QueryString:        "method=string",
			ExpectedStatusCode: 400,
		},
		{
			Name:               "Empty swarmStackFromFileContentPayload with repository method",
			Payload:            swarmStackFromFileContentPayload{},
			QueryString:        "method=repository",
			ExpectedStatusCode: 400,
		},
		{
			Name:               "Empty swarmStackFromFileContentPayload with file method",
			Payload:            swarmStackFromFileContentPayload{},
			QueryString:        "method=file",
			ExpectedStatusCode: 400,
		},
		{
			Name: "Duplicated EdgeStack Name",
			Payload: swarmStackFromFileContentPayload{
				Name:             edgeStack.Name,
				StackFileContent: "content",
				EdgeGroups:       edgeStack.EdgeGroups,
				DeploymentType:   edgeStack.DeploymentType,
			},
			QueryString:        "method=string",
			ExpectedStatusCode: 500,
		},
		{
			Name: "Empty EdgeStack Groups",
			Payload: swarmStackFromFileContentPayload{
				Name:             edgeStack.Name,
				StackFileContent: "content",
				EdgeGroups:       []portainer.EdgeGroupID{},
				DeploymentType:   edgeStack.DeploymentType,
			},
			QueryString:        "method=string",
			ExpectedStatusCode: 400,
		},
		{
			Name: "EdgeStackDeploymentKubernetes with Docker endpoint",
			Payload: swarmStackFromFileContentPayload{
				Name:             "Stack name",
				StackFileContent: "content",
				EdgeGroups:       []portainer.EdgeGroupID{1},
				DeploymentType:   portainer.EdgeStackDeploymentKubernetes,
			},
			QueryString:        "method=string",
			ExpectedStatusCode: 500,
		},
		{
			Name: "Empty Stack File Content",
			Payload: swarmStackFromFileContentPayload{
				Name:             "Stack name",
				StackFileContent: "",
				EdgeGroups:       []portainer.EdgeGroupID{1},
				DeploymentType:   portainer.EdgeStackDeploymentCompose,
			},
			QueryString:        "method=string",
			ExpectedStatusCode: 400,
		},
		{
			Name: "Clone Git respository error",
			Payload: swarmStackFromGitRepositoryPayload{
				Name:                     "Stack name",
				RepositoryURL:            "github.com/portainer/portainer",
				RepositoryReferenceName:  "ref name",
				RepositoryAuthentication: false,
				RepositoryUsername:       "",
				RepositoryPassword:       "",
				FilePathInRepository:     "/file/path",
				EdgeGroups:               []portainer.EdgeGroupID{1},
				DeploymentType:           portainer.EdgeStackDeploymentCompose,
			},
			QueryString:        "method=repository",
			ExpectedStatusCode: 500,
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			jsonPayload, err := json.Marshal(tc.Payload)
			if err != nil {
				t.Fatal("JSON marshal error:", err)
			}
			r := bytes.NewBuffer(jsonPayload)

			// Create EdgeStack
			req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("/edge_stacks?%s", tc.QueryString), r)
			if err != nil {
				t.Fatal("request error:", err)
			}

			req.Header.Add("x-api-key", rawAPIKey)
			rec := httptest.NewRecorder()
			handler.ServeHTTP(rec, req)

			if rec.Code != tc.ExpectedStatusCode {
				t.Fatalf("expected a %d response, found: %d", tc.ExpectedStatusCode, rec.Code)
			}
		})
	}
}

// Delete
func TestDeleteAndInspect(t *testing.T) {
	handler, rawAPIKey, teardown := setupHandler(t)
	defer teardown()

	// Create
	endpoint := createEndpoint(t, handler.DataStore)
	edgeStack := createEdgeStack(t, handler.DataStore, endpoint.ID)

	// Inspect
	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/edge_stacks/%d", edgeStack.ID), nil)
	if err != nil {
		t.Fatal("request error:", err)
	}

	req.Header.Add("x-api-key", rawAPIKey)
	rec := httptest.NewRecorder()
	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
	}

	data := portainer.EdgeStack{}
	err = json.NewDecoder(rec.Body).Decode(&data)
	if err != nil {
		t.Fatal("error decoding response:", err)
	}

	if data.ID != edgeStack.ID {
		t.Fatalf("expected EdgeStackID %d, found %d", int(edgeStack.ID), data.ID)
	}

	// Delete
	req, err = http.NewRequest(http.MethodDelete, fmt.Sprintf("/edge_stacks/%d", edgeStack.ID), nil)
	if err != nil {
		t.Fatal("request error:", err)
	}

	req.Header.Add("x-api-key", rawAPIKey)
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusNoContent {
		t.Fatalf("expected a %d response, found: %d", http.StatusNoContent, rec.Code)
	}

	// Inspect
	req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/edge_stacks/%d", edgeStack.ID), nil)
	if err != nil {
		t.Fatal("request error:", err)
	}

	req.Header.Add("x-api-key", rawAPIKey)
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusNotFound {
		t.Fatalf("expected a %d response, found: %d", http.StatusNotFound, rec.Code)
	}
}

func TestDeleteInvalidEdgeStack(t *testing.T) {
	handler, rawAPIKey, teardown := setupHandler(t)
	defer teardown()

	cases := []struct {
		Name               string
		URL                string
		ExpectedStatusCode int
	}{
		{Name: "Non-existing EdgeStackID", URL: "/edge_stacks/-1", ExpectedStatusCode: http.StatusNotFound},
		{Name: "Invalid EdgeStackID", URL: "/edge_stacks/aaaaaaa", ExpectedStatusCode: http.StatusBadRequest},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			req, err := http.NewRequest(http.MethodDelete, tc.URL, nil)
			if err != nil {
				t.Fatal("request error:", err)
			}

			req.Header.Add("x-api-key", rawAPIKey)
			rec := httptest.NewRecorder()
			handler.ServeHTTP(rec, req)

			if rec.Code != tc.ExpectedStatusCode {
				t.Fatalf("expected a %d response, found: %d", tc.ExpectedStatusCode, rec.Code)
			}
		})
	}
}

// Update
func TestUpdateAndInspect(t *testing.T) {
	handler, rawAPIKey, teardown := setupHandler(t)
	defer teardown()

	endpoint := createEndpoint(t, handler.DataStore)
	edgeStack := createEdgeStack(t, handler.DataStore, endpoint.ID)

	// Update edge stack: create new Endpoint, EndpointRelation and EdgeGroup
	endpointID := portainer.EndpointID(6)
	newEndpoint := createEndpointWithId(t, handler.DataStore, endpointID)

	err := handler.DataStore.Endpoint().Create(&newEndpoint)
	if err != nil {
		t.Fatal(err)
	}

	endpointRelation := portainer.EndpointRelation{
		EndpointID: endpointID,
		EdgeStacks: map[portainer.EdgeStackID]bool{
			edgeStack.ID: true,
		},
	}

	err = handler.DataStore.EndpointRelation().Create(&endpointRelation)
	if err != nil {
		t.Fatal(err)
	}

	newEdgeGroup := portainer.EdgeGroup{
		ID:           2,
		Name:         "EdgeGroup 2",
		Dynamic:      false,
		TagIDs:       nil,
		Endpoints:    []portainer.EndpointID{newEndpoint.ID},
		PartialMatch: false,
	}

	err = handler.DataStore.EdgeGroup().Create(&newEdgeGroup)
	if err != nil {
		t.Fatal(err)
	}

	newVersion := 238
	payload := updateEdgeStackPayload{
		StackFileContent: "update-test",
		Version:          &newVersion,
		EdgeGroups:       append(edgeStack.EdgeGroups, newEdgeGroup.ID),
		DeploymentType:   portainer.EdgeStackDeploymentCompose,
	}

	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		t.Fatal("request error:", err)
	}

	r := bytes.NewBuffer(jsonPayload)
	req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("/edge_stacks/%d", edgeStack.ID), r)
	if err != nil {
		t.Fatal("request error:", err)
	}

	req.Header.Add("x-api-key", rawAPIKey)
	rec := httptest.NewRecorder()
	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
	}

	// Get updated edge stack
	req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/edge_stacks/%d", edgeStack.ID), nil)
	if err != nil {
		t.Fatal("request error:", err)
	}

	req.Header.Add("x-api-key", rawAPIKey)
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
	}

	data := portainer.EdgeStack{}
	err = json.NewDecoder(rec.Body).Decode(&data)
	if err != nil {
		t.Fatal("error decoding response:", err)
	}

	if data.Version != *payload.Version {
		t.Fatalf("expected EdgeStackID %d, found %d", edgeStack.Version, data.Version)
	}

	if data.DeploymentType != payload.DeploymentType {
		t.Fatalf("expected DeploymentType %d, found %d", edgeStack.DeploymentType, data.DeploymentType)
	}

	if !reflect.DeepEqual(data.EdgeGroups, payload.EdgeGroups) {
		t.Fatalf("expected EdgeGroups to be equal")
	}
}

func TestUpdateWithInvalidEdgeGroups(t *testing.T) {
	handler, rawAPIKey, teardown := setupHandler(t)
	defer teardown()

	endpoint := createEndpoint(t, handler.DataStore)
	edgeStack := createEdgeStack(t, handler.DataStore, endpoint.ID)

	//newEndpoint := createEndpoint(t, handler.DataStore)
	newEdgeGroup := portainer.EdgeGroup{
		ID:           2,
		Name:         "EdgeGroup 2",
		Dynamic:      false,
		TagIDs:       nil,
		Endpoints:    []portainer.EndpointID{8889},
		PartialMatch: false,
	}

	handler.DataStore.EdgeGroup().Create(&newEdgeGroup)

	newVersion := 238
	cases := []struct {
		Name               string
		Payload            updateEdgeStackPayload
		ExpectedStatusCode int
	}{
		{
			"Update with non-existing EdgeGroupID",
			updateEdgeStackPayload{
				StackFileContent: "error-test",
				Version:          &newVersion,
				EdgeGroups:       []portainer.EdgeGroupID{9999},
				DeploymentType:   edgeStack.DeploymentType,
			},
			http.StatusInternalServerError,
		},
		{
			"Update with invalid EdgeGroup (non-existing Endpoint)",
			updateEdgeStackPayload{
				StackFileContent: "error-test",
				Version:          &newVersion,
				EdgeGroups:       []portainer.EdgeGroupID{2},
				DeploymentType:   edgeStack.DeploymentType,
			},
			http.StatusInternalServerError,
		},
		{
			"Update DeploymentType from Docker to Kubernetes",
			updateEdgeStackPayload{
				StackFileContent: "error-test",
				Version:          &newVersion,
				EdgeGroups:       []portainer.EdgeGroupID{1},
				DeploymentType:   portainer.EdgeStackDeploymentKubernetes,
			},
			http.StatusBadRequest,
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			jsonPayload, err := json.Marshal(tc.Payload)
			if err != nil {
				t.Fatal("JSON marshal error:", err)
			}

			r := bytes.NewBuffer(jsonPayload)
			req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("/edge_stacks/%d", edgeStack.ID), r)
			if err != nil {
				t.Fatal("request error:", err)
			}

			req.Header.Add("x-api-key", rawAPIKey)
			rec := httptest.NewRecorder()
			handler.ServeHTTP(rec, req)

			if rec.Code != tc.ExpectedStatusCode {
				t.Fatalf("expected a %d response, found: %d", tc.ExpectedStatusCode, rec.Code)
			}
		})
	}
}

func TestUpdateWithInvalidPayload(t *testing.T) {
	handler, rawAPIKey, teardown := setupHandler(t)
	defer teardown()

	endpoint := createEndpoint(t, handler.DataStore)
	edgeStack := createEdgeStack(t, handler.DataStore, endpoint.ID)

	newVersion := 238
	cases := []struct {
		Name               string
		Payload            updateEdgeStackPayload
		ExpectedStatusCode int
	}{
		{
			"Update with empty StackFileContent",
			updateEdgeStackPayload{
				StackFileContent: "",
				Version:          &newVersion,
				EdgeGroups:       edgeStack.EdgeGroups,
				DeploymentType:   edgeStack.DeploymentType,
			},
			http.StatusBadRequest,
		},
		{
			"Update with empty EdgeGroups",
			updateEdgeStackPayload{
				StackFileContent: "error-test",
				Version:          &newVersion,
				EdgeGroups:       []portainer.EdgeGroupID{},
				DeploymentType:   edgeStack.DeploymentType,
			},
			http.StatusBadRequest,
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			jsonPayload, err := json.Marshal(tc.Payload)
			if err != nil {
				t.Fatal("request error:", err)
			}

			r := bytes.NewBuffer(jsonPayload)
			req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("/edge_stacks/%d", edgeStack.ID), r)
			if err != nil {
				t.Fatal("request error:", err)
			}

			req.Header.Add("x-api-key", rawAPIKey)
			rec := httptest.NewRecorder()
			handler.ServeHTTP(rec, req)

			if rec.Code != tc.ExpectedStatusCode {
				t.Fatalf("expected a %d response, found: %d", tc.ExpectedStatusCode, rec.Code)
			}
		})
	}
}

// Update Status
func TestUpdateStatusAndInspect(t *testing.T) {
	handler, rawAPIKey, teardown := setupHandler(t)
	defer teardown()

	endpoint := createEndpoint(t, handler.DataStore)
	edgeStack := createEdgeStack(t, handler.DataStore, endpoint.ID)

	// Update edge stack status
	newStatus := portainer.EdgeStackStatusError
	payload := updateStatusPayload{
		Error:      "test-error",
		Status:     &newStatus,
		EndpointID: endpoint.ID,
	}

	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		t.Fatal("request error:", err)
	}

	r := bytes.NewBuffer(jsonPayload)
	req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("/edge_stacks/%d/status", edgeStack.ID), r)
	if err != nil {
		t.Fatal("request error:", err)
	}

	req.Header.Set(portainer.PortainerAgentEdgeIDHeader, endpoint.EdgeID)
	rec := httptest.NewRecorder()
	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
	}

	// Get updated edge stack
	req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/edge_stacks/%d", edgeStack.ID), nil)
	if err != nil {
		t.Fatal("request error:", err)
	}

	req.Header.Add("x-api-key", rawAPIKey)
	rec = httptest.NewRecorder()
	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
	}

	data := portainer.EdgeStack{}
	err = json.NewDecoder(rec.Body).Decode(&data)
	if err != nil {
		t.Fatal("error decoding response:", err)
	}

	if !data.Status[endpoint.ID].Details.Error {
		t.Fatalf("expected EdgeStackStatusType %d, found %t", payload.Status, data.Status[endpoint.ID].Details.Error)
	}

	if data.Status[endpoint.ID].Error != payload.Error {
		t.Fatalf("expected EdgeStackStatusError %s, found %s", payload.Error, data.Status[endpoint.ID].Error)
	}

	if data.Status[endpoint.ID].EndpointID != payload.EndpointID {
		t.Fatalf("expected EndpointID %d, found %d", payload.EndpointID, data.Status[endpoint.ID].EndpointID)
	}
}
func TestUpdateStatusWithInvalidPayload(t *testing.T) {
	handler, _, teardown := setupHandler(t)
	defer teardown()

	endpoint := createEndpoint(t, handler.DataStore)
	edgeStack := createEdgeStack(t, handler.DataStore, endpoint.ID)

	// Update edge stack status
	statusError := portainer.EdgeStackStatusError
	statusOk := portainer.EdgeStackStatusOk
	cases := []struct {
		Name                 string
		Payload              updateStatusPayload
		ExpectedErrorMessage string
		ExpectedStatusCode   int
	}{
		{
			"Update with nil Status",
			updateStatusPayload{
				Error:      "test-error",
				Status:     nil,
				EndpointID: endpoint.ID,
			},
			"Invalid status",
			400,
		},
		{
			"Update with error status and empty error message",
			updateStatusPayload{
				Error:      "",
				Status:     &statusError,
				EndpointID: endpoint.ID,
			},
			"Error message is mandatory when status is error",
			400,
		},
		{
			"Update with missing EndpointID",
			updateStatusPayload{
				Error:      "",
				Status:     &statusOk,
				EndpointID: 0,
			},
			"Invalid EnvironmentID",
			400,
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			jsonPayload, err := json.Marshal(tc.Payload)
			if err != nil {
				t.Fatal("request error:", err)
			}

			r := bytes.NewBuffer(jsonPayload)
			req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("/edge_stacks/%d/status", edgeStack.ID), r)
			if err != nil {
				t.Fatal("request error:", err)
			}

			req.Header.Set(portainer.PortainerAgentEdgeIDHeader, endpoint.EdgeID)
			rec := httptest.NewRecorder()
			handler.ServeHTTP(rec, req)

			if rec.Code != tc.ExpectedStatusCode {
				t.Fatalf("expected a %d response, found: %d", tc.ExpectedStatusCode, rec.Code)
			}
		})
	}
}

// Delete Status
func TestDeleteStatus(t *testing.T) {
	handler, _, teardown := setupHandler(t)
	defer teardown()

	endpoint := createEndpoint(t, handler.DataStore)
	edgeStack := createEdgeStack(t, handler.DataStore, endpoint.ID)

	req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("/edge_stacks/%d/status/%d", edgeStack.ID, endpoint.ID), nil)
	if err != nil {
		t.Fatal("request error:", err)
	}

	req.Header.Set(portainer.PortainerAgentEdgeIDHeader, endpoint.EdgeID)
	rec := httptest.NewRecorder()
	handler.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
	}
}