package endpoints

import (
	"context"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"sync/atomic"
	"testing"

	portainer "github.com/portainer/portainer/api"
	"github.com/portainer/portainer/api/datastore"
	"github.com/portainer/portainer/api/http/security"
	"github.com/portainer/portainer/api/internal/testhelpers"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func Test_endpointSnapshots(t *testing.T) {
	t.Parallel()
	_, store := datastore.MustNewTestStore(t, false, true)

	endpointID := portainer.EndpointID(123)
	endpoint := &portainer.Endpoint{
		ID:     endpointID,
		Name:   "mock",
		URL:    "http://mock.example/",
		Status: portainer.EndpointStatusDown, // starts in down state
	}
	err := store.Endpoint().Create(endpoint)

	require.NoError(t, err, "error creating environment")

	err = store.User().Create(
		&portainer.User{
			Username: "admin",
			Role:     portainer.AdministratorRole,
		},
	)
	require.NoError(t, err, "error creating a user")

	bouncer := testhelpers.NewTestRequestBouncer()

	snapshotService := &mockSnapshotService{
		snapshotEndpointShouldSucceed: atomic.Bool{},
	}
	snapshotService.snapshotEndpointShouldSucceed.Store(true)

	h := NewHandler(bouncer)
	h.DataStore = store
	h.SnapshotService = snapshotService

	doPostRequest := func() {
		req := httptest.NewRequest(http.MethodPost, "/endpoints/snapshot", nil)
		ctx := security.StoreTokenData(req, &portainer.TokenData{ID: 1, Username: "admin", Role: 1})
		req = req.WithContext(ctx)
		testhelpers.AddTestSecurityCookie(req, "Bearer dummytoken")

		rr := httptest.NewRecorder()
		h.ServeHTTP(rr, req)

		require.Equal(t, http.StatusNoContent, rr.Code, "Status should be 204")

		_, err := io.ReadAll(rr.Body)
		require.NoError(t, err, "ReadAll should not return error")
	}

	doPostRequest()

	// check that the endpoint has been immediately set to up
	endpoint, err = store.Endpoint().Endpoint(endpointID)
	require.NoError(t, err, "error getting endpoint")
	assert.Equal(t, portainer.EndpointStatusUp, endpoint.Status, "endpoint should be up (1) since mock snapshot returned ok")

	// set the mock to return an error
	snapshotService.snapshotEndpointShouldSucceed.Store(false)
	doPostRequest()

	// check that the endpoint has been immediately set to down
	endpoint, err = store.Endpoint().Endpoint(endpointID)
	require.NoError(t, err, "error getting endpoint")
	assert.Equal(t, portainer.EndpointStatusDown, endpoint.Status, "endpoint should be down (2) since mock snapshot returned error")
}

var _ portainer.SnapshotService = &mockSnapshotService{}

type mockSnapshotService struct {
	snapshotEndpointShouldSucceed atomic.Bool
}

func (s *mockSnapshotService) Start(_ context.Context) {
}

func (s *mockSnapshotService) SetSnapshotInterval(snapshotInterval string) error {
	return nil
}

func (s *mockSnapshotService) SnapshotEndpoint(endpoint *portainer.Endpoint) error {
	if s.snapshotEndpointShouldSucceed.Load() {
		return nil
	}

	return errors.New("snapshot failed")
}

func (s *mockSnapshotService) FillSnapshotData(endpoint *portainer.Endpoint, includeRaw bool) error {
	return nil
}
