package edgejobs

import (
	"bytes"
	"encoding/json"
	"io"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/gorilla/mux"
	portainer "github.com/portainer/portainer/api"
	"github.com/portainer/portainer/api/dataservices"
	"github.com/portainer/portainer/api/datastore"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
)

type mockFileService struct {
	mock.Mock
	portainer.FileService
}

func (m *mockFileService) StoreEdgeJobFileFromBytes(id string, file []byte) (string, error) {
	args := m.Called(id, file)
	return args.String(0), args.Error(1)
}

func (m *mockFileService) GetEdgeJobFolder(id string) string {
	args := m.Called(id)

	return args.String(0)
}

func (m *mockFileService) RemoveDirectory(path string) error {
	args := m.Called(path)

	return args.Error(0)
}

func initStore(t *testing.T) *datastore.Store {
	_, store := datastore.MustNewTestStore(t, true, true)
	require.NotNil(t, store)

	require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
		require.NoError(t, tx.Endpoint().Create(&portainer.Endpoint{
			ID:          1,
			Name:        "endpoint-1",
			EdgeID:      "edge-id-1",
			GroupID:     1,
			Type:        portainer.EdgeAgentOnDockerEnvironment,
			UserTrusted: true,
		}))

		require.NoError(t, tx.Endpoint().Create(&portainer.Endpoint{
			ID:          2,
			Name:        "endpoint-2",
			EdgeID:      "edge-id-2",
			GroupID:     1,
			Type:        portainer.EdgeAgentOnDockerEnvironment,
			UserTrusted: false,
		}))
		return nil
	}))

	return store
}

func Test_edgeJobCreate_StringMethod_Success(t *testing.T) {
	t.Parallel()
	store := initStore(t)

	fileService := &mockFileService{}
	fileService.On("StoreEdgeJobFileFromBytes", mock.Anything, mock.Anything).Return("testfile.txt", nil)

	handler := &Handler{
		DataStore:   store,
		FileService: fileService,
	}

	payload := edgeJobCreateFromFileContentPayload{
		edgeJobBasePayload: edgeJobBasePayload{
			Name:           "testjob",
			CronExpression: "* * * * *",
			Endpoints:      []portainer.EndpointID{1, 2},
		},
		FileContent: "echo hello",
	}

	body, _ := json.Marshal(payload)
	req := httptest.NewRequest(http.MethodPost, "/edge_jobs/create/string", bytes.NewReader(body))
	req = mux.SetURLVars(req, map[string]string{"method": "string"})
	w := httptest.NewRecorder()

	// Call handler
	errh := handler.edgeJobCreate(w, req)
	require.Nil(t, errh)
	require.Equal(t, http.StatusOK, w.Result().StatusCode)

	// Get edge job ID from response
	var resp struct {
		ID int `json:"Id"`
	}
	require.NoError(t, json.NewDecoder(w.Body).Decode(&resp))

	edgeJob, err := store.EdgeJob().Read(portainer.EdgeJobID(resp.ID))
	require.NoError(t, err)

	require.Len(t, edgeJob.Endpoints, 2)
	require.Contains(t, edgeJob.Endpoints, portainer.EndpointID(1))
}

func Test_edgeJobCreate_FileMethod_Success(t *testing.T) {
	t.Parallel()
	store := initStore(t)

	fileService := &mockFileService{}
	fileService.On("StoreEdgeJobFileFromBytes", mock.Anything, mock.Anything).Return("testfile.txt", nil)

	handler := &Handler{
		DataStore:   store,
		FileService: fileService,
	}

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	require.NoError(t, writer.WriteField("Name", "testjob"))
	require.NoError(t, writer.WriteField("CronExpression", "* * * * *"))
	require.NoError(t, writer.WriteField("Endpoints", "[1,2]"))

	fileWriter, err := writer.CreateFormFile("file", "test.txt")
	require.NoError(t, err)

	_, err = io.Copy(fileWriter, strings.NewReader("echo hello"))
	require.NoError(t, err)
	require.NoError(t, writer.Close())

	req := httptest.NewRequest(http.MethodPost, "/edge_jobs/create/file", &body)
	req = mux.SetURLVars(req, map[string]string{"method": "file"})
	req.Header.Set("Content-Type", writer.FormDataContentType())

	w := httptest.NewRecorder()
	handlerErr := handler.edgeJobCreate(w, req)
	require.Nil(t, handlerErr)
	require.Equal(t, http.StatusOK, w.Result().StatusCode)

	var resp struct {
		ID int `json:"Id"`
	}
	require.NoError(t, json.NewDecoder(w.Body).Decode(&resp))

	edgeJob, err := store.EdgeJob().Read(portainer.EdgeJobID(resp.ID))
	require.NoError(t, err)

	require.Len(t, edgeJob.Endpoints, 2)
	require.Contains(t, edgeJob.Endpoints, portainer.EndpointID(1))
}
