package customtemplates

import (
	"bytes"
	"context"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	portainer "github.com/portainer/portainer/api"
	"github.com/portainer/portainer/api/dataservices"
	"github.com/portainer/portainer/api/filesystem"
	gittypes "github.com/portainer/portainer/api/git/types"
	"github.com/portainer/portainer/api/http/security"

	"github.com/gorilla/mux"
	"github.com/segmentio/encoding/json"
	"github.com/stretchr/testify/require"
)

func createTemplateRequest(t *testing.T, method string, payload any, userID portainer.UserID, role portainer.UserRole) *http.Request {
	t.Helper()

	body, err := json.Marshal(payload)
	require.NoError(t, err)

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/"+method, bytes.NewReader(body))
	r.Header.Set("Content-Type", "application/json")
	r = mux.SetURLVars(r, map[string]string{"method": method})

	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: userID, Role: role})
	r = r.WithContext(ctx)
	ctx = security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  userID,
		IsAdmin: role == portainer.AdministratorRole,
		User:    &portainer.User{ID: userID, Role: role},
	})
	return r.WithContext(ctx)
}

func TestCustomTemplateCreate_FromFileContent_Success(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:       "My Template",
		Description: "A test template",
		FileContent: "version: '3'\nservices:\n  web:\n    image: nginx",
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.Nil(t, herr)
	require.Equal(t, http.StatusOK, rr.Code)

	var tmpl portainer.CustomTemplate
	require.NoError(t, json.NewDecoder(rr.Body).Decode(&tmpl))
	require.Equal(t, "My Template", tmpl.Title)
	require.Equal(t, "A test template", tmpl.Description)
	require.Equal(t, portainer.UserID(1), tmpl.CreatedByUserID)
	require.NotNil(t, tmpl.ResourceControl)

	err := ds.ViewTx(func(tx dataservices.DataStoreTx) error {
		templates, err := tx.CustomTemplate().ReadAll()
		require.NoError(t, err)
		require.Len(t, templates, 1)
		require.Equal(t, "My Template", templates[0].Title)

		rcs, err := tx.ResourceControl().ReadAll()
		require.NoError(t, err)
		require.Len(t, rcs, 1)

		return nil
	})
	require.NoError(t, err)
}

func TestCustomTemplateCreate_FromFileContent_DuplicateTitle(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)

	require.NoError(t, ds.UpdateTx(func(tx dataservices.DataStoreTx) error {
		return tx.CustomTemplate().Create(&portainer.CustomTemplate{
			ID:    1,
			Title: "Existing Template",
		})
	}))

	payload := customTemplateFromFileContentPayload{
		Title:       "Existing Template",
		Description: "Another template",
		FileContent: "version: '3'",
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileContent_MissingTitle(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Description: "A test template",
		FileContent: "version: '3'",
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileContent_MissingDescription(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:       "My Template",
		FileContent: "version: '3'",
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileContent_MissingFileContent(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:       "My Template",
		Description: "A test template",
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileContent_InvalidPlatform(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:       "My Template",
		Description: "A test template",
		FileContent: "version: '3'",
		Type:        portainer.DockerComposeStack,
		Platform:    0,
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileContent_NoteWithImage(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:       "My Template",
		Description: "A test template",
		FileContent: "version: '3'",
		Note:        `Some note with <img src="x" onerror="alert(1)">`,
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileContent_KubernetesTypeIgnoresPlatform(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:       "K8s Template",
		Description: "A kubernetes template",
		FileContent: "apiVersion: v1\nkind: Pod",
		Type:        portainer.KubernetesStack,
		Platform:    0,
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.Nil(t, herr)
	require.Equal(t, http.StatusOK, rr.Code)

	var tmpl portainer.CustomTemplate
	require.NoError(t, json.NewDecoder(rr.Body).Decode(&tmpl))
	require.Equal(t, "K8s Template", tmpl.Title)
	require.Equal(t, portainer.KubernetesStack, tmpl.Type)

	err := ds.ViewTx(func(tx dataservices.DataStoreTx) error {
		templates, err := tx.CustomTemplate().ReadAll()
		require.NoError(t, err)
		require.Len(t, templates, 1)

		return nil
	})
	require.NoError(t, err)
}

func TestCustomTemplateCreate_FromFileUpload_Success(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	err := writer.WriteField("Title", "Uploaded Template")
	require.NoError(t, err)

	err = writer.WriteField("Description", "Uploaded from file")
	require.NoError(t, err)

	err = writer.WriteField("Type", "2")
	require.NoError(t, err)

	err = writer.WriteField("Platform", "1")
	require.NoError(t, err)

	part, err := writer.CreateFormFile("File", "docker-compose.yml")
	require.NoError(t, err)

	_, err = part.Write([]byte("version: '3'\nservices:\n  web:\n    image: nginx"))
	require.NoError(t, err)

	require.NoError(t, writer.Close())

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/file", &body)
	r.Header.Set("Content-Type", writer.FormDataContentType())
	r = mux.SetURLVars(r, map[string]string{"method": "file"})
	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: 1, Role: portainer.AdministratorRole})
	r = r.WithContext(ctx)
	r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  1,
		IsAdmin: true,
		User:    &portainer.User{ID: 1, Role: portainer.AdministratorRole},
	}))

	rr := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr, r)
	require.Nil(t, herr)
	require.Equal(t, http.StatusOK, rr.Code)

	var tmpl portainer.CustomTemplate
	require.NoError(t, json.NewDecoder(rr.Body).Decode(&tmpl))
	require.Equal(t, "Uploaded Template", tmpl.Title)
	require.Equal(t, filesystem.ComposeFileDefaultName, tmpl.EntryPoint)

	err = ds.ViewTx(func(tx dataservices.DataStoreTx) error {
		templates, err := tx.CustomTemplate().ReadAll()
		require.NoError(t, err)
		require.Len(t, templates, 1)

		return nil
	})
	require.NoError(t, err)
}

func TestCustomTemplateCreate_InvalidMethod(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:       "My Template",
		Description: "A test template",
		FileContent: "version: '3'",
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "invalid", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileContent_InvalidType(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:       "My Template",
		Description: "A test template",
		FileContent: "version: '3'",
		Type:        0,
		Platform:    portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileContent_Variables(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:       "Template With Variables",
		Description: "A test template",
		FileContent: "version: '3'",
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
		Variables: []portainer.CustomTemplateVariableDefinition{
			{Name: "IMAGE", Label: "Docker image"},
		},
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.Nil(t, herr)
	require.Equal(t, http.StatusOK, rr.Code)

	var tmpl portainer.CustomTemplate
	require.NoError(t, json.NewDecoder(rr.Body).Decode(&tmpl))
	require.Len(t, tmpl.Variables, 1)
	require.Equal(t, "IMAGE", tmpl.Variables[0].Name)

	err := ds.ViewTx(func(tx dataservices.DataStoreTx) error {
		stored, err := tx.CustomTemplate().Read(tmpl.ID)
		require.NoError(t, err)
		require.Len(t, stored.Variables, 1)

		return nil
	})
	require.NoError(t, err)
}

func TestCustomTemplateCreate_FromFileContent_InvalidVariables(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:       "My Template",
		Description: "A test template",
		FileContent: "version: '3'",
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
		Variables: []portainer.CustomTemplateVariableDefinition{
			{Label: "Missing name"},
		},
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileContent_EdgeTemplate(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)

	payload := customTemplateFromFileContentPayload{
		Title:        "Edge Template",
		Description:  "For edge stacks",
		FileContent:  "version: '3'\nservices:\n  web:\n    image: nginx",
		Type:         portainer.DockerComposeStack,
		Platform:     portainer.CustomTemplatePlatformLinux,
		EdgeTemplate: true,
	}

	r := createTemplateRequest(t, "string", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.Nil(t, herr)
	require.Equal(t, http.StatusOK, rr.Code)

	var tmpl portainer.CustomTemplate
	require.NoError(t, json.NewDecoder(rr.Body).Decode(&tmpl))
	require.True(t, tmpl.EdgeTemplate)

	err := ds.ViewTx(func(tx dataservices.DataStoreTx) error {
		stored, err := tx.CustomTemplate().Read(tmpl.ID)
		require.NoError(t, err)
		require.True(t, stored.EdgeTemplate)

		return nil
	})
	require.NoError(t, err)
}

func TestCustomTemplateCreate_FromFileUpload_MissingTitle(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	err := writer.WriteField("Description", "A description")
	require.NoError(t, err)

	err = writer.WriteField("Type", "2")
	require.NoError(t, err)

	err = writer.WriteField("Platform", "1")
	require.NoError(t, err)

	part, err := writer.CreateFormFile("File", "docker-compose.yml")
	require.NoError(t, err)

	_, err = part.Write([]byte("version: '3'"))
	require.NoError(t, err)

	require.NoError(t, writer.Close())

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/file", &body)
	r.Header.Set("Content-Type", writer.FormDataContentType())
	r = mux.SetURLVars(r, map[string]string{"method": "file"})
	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: 1, Role: portainer.AdministratorRole})
	r = r.WithContext(ctx)
	r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  1,
		IsAdmin: true,
		User:    &portainer.User{ID: 1, Role: portainer.AdministratorRole},
	}))

	rr := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileUpload_MissingDescription(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	err := writer.WriteField("Title", "My Template")
	require.NoError(t, err)

	err = writer.WriteField("Type", "2")
	require.NoError(t, err)

	err = writer.WriteField("Platform", "1")
	require.NoError(t, err)

	part, err := writer.CreateFormFile("File", "docker-compose.yml")
	require.NoError(t, err)

	_, err = part.Write([]byte("version: '3'"))
	require.NoError(t, err)

	require.NoError(t, writer.Close())

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/file", &body)
	r.Header.Set("Content-Type", writer.FormDataContentType())
	r = mux.SetURLVars(r, map[string]string{"method": "file"})
	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: 1, Role: portainer.AdministratorRole})
	r = r.WithContext(ctx)
	r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  1,
		IsAdmin: true,
		User:    &portainer.User{ID: 1, Role: portainer.AdministratorRole},
	}))

	rr := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileUpload_MissingFile(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	err := writer.WriteField("Title", "My Template")
	require.NoError(t, err)

	err = writer.WriteField("Description", "A description")
	require.NoError(t, err)

	err = writer.WriteField("Type", "2")
	require.NoError(t, err)

	err = writer.WriteField("Platform", "1")
	require.NoError(t, err)

	require.NoError(t, writer.Close())

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/file", &body)
	r.Header.Set("Content-Type", writer.FormDataContentType())
	r = mux.SetURLVars(r, map[string]string{"method": "file"})
	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: 1, Role: portainer.AdministratorRole})
	r = r.WithContext(ctx)
	r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  1,
		IsAdmin: true,
		User:    &portainer.User{ID: 1, Role: portainer.AdministratorRole},
	}))

	rr := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileUpload_InvalidType(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	err := writer.WriteField("Title", "My Template")
	require.NoError(t, err)

	err = writer.WriteField("Description", "A description")
	require.NoError(t, err)

	err = writer.WriteField("Type", "0")
	require.NoError(t, err)

	err = writer.WriteField("Platform", "1")
	require.NoError(t, err)

	part, err := writer.CreateFormFile("File", "docker-compose.yml")
	require.NoError(t, err)

	_, err = part.Write([]byte("version: '3'"))
	require.NoError(t, err)

	require.NoError(t, writer.Close())

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/file", &body)
	r.Header.Set("Content-Type", writer.FormDataContentType())
	r = mux.SetURLVars(r, map[string]string{"method": "file"})
	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: 1, Role: portainer.AdministratorRole})
	r = r.WithContext(ctx)
	r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  1,
		IsAdmin: true,
		User:    &portainer.User{ID: 1, Role: portainer.AdministratorRole},
	}))

	rr := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileUpload_InvalidPlatform(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	err := writer.WriteField("Title", "My Template")
	require.NoError(t, err)

	err = writer.WriteField("Description", "A description")
	require.NoError(t, err)

	err = writer.WriteField("Type", "2")
	require.NoError(t, err)

	err = writer.WriteField("Platform", "0")
	require.NoError(t, err)

	part, err := writer.CreateFormFile("File", "docker-compose.yml")
	require.NoError(t, err)

	_, err = part.Write([]byte("version: '3'"))
	require.NoError(t, err)

	require.NoError(t, writer.Close())

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/file", &body)
	r.Header.Set("Content-Type", writer.FormDataContentType())
	r = mux.SetURLVars(r, map[string]string{"method": "file"})
	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: 1, Role: portainer.AdministratorRole})
	r = r.WithContext(ctx)
	r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  1,
		IsAdmin: true,
		User:    &portainer.User{ID: 1, Role: portainer.AdministratorRole},
	}))

	rr := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileUpload_NoteWithImage(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	err := writer.WriteField("Title", "My Template")
	require.NoError(t, err)

	err = writer.WriteField("Description", "A description")
	require.NoError(t, err)

	err = writer.WriteField("Note", `Some note <img src="x" onerror="alert(1)">`)
	require.NoError(t, err)

	err = writer.WriteField("Type", "2")
	require.NoError(t, err)

	err = writer.WriteField("Platform", "1")
	require.NoError(t, err)

	part, err := writer.CreateFormFile("File", "docker-compose.yml")
	require.NoError(t, err)

	_, err = part.Write([]byte("version: '3'"))
	require.NoError(t, err)

	require.NoError(t, writer.Close())

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/file", &body)
	r.Header.Set("Content-Type", writer.FormDataContentType())
	r = mux.SetURLVars(r, map[string]string{"method": "file"})
	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: 1, Role: portainer.AdministratorRole})
	r = r.WithContext(ctx)
	r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  1,
		IsAdmin: true,
		User:    &portainer.User{ID: 1, Role: portainer.AdministratorRole},
	}))

	rr := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromFileUpload_KubernetesIgnoresPlatform(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	err := writer.WriteField("Title", "K8s Upload Template")
	require.NoError(t, err)

	err = writer.WriteField("Description", "A kubernetes template")
	require.NoError(t, err)

	err = writer.WriteField("Type", "3")
	require.NoError(t, err)

	err = writer.WriteField("Platform", "0")
	require.NoError(t, err)

	part, err := writer.CreateFormFile("File", "deployment.yml")
	require.NoError(t, err)

	_, err = part.Write([]byte("apiVersion: v1\nkind: Pod"))
	require.NoError(t, err)

	require.NoError(t, writer.Close())

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/file", &body)
	r.Header.Set("Content-Type", writer.FormDataContentType())
	r = mux.SetURLVars(r, map[string]string{"method": "file"})
	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: 1, Role: portainer.AdministratorRole})
	r = r.WithContext(ctx)
	r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  1,
		IsAdmin: true,
		User:    &portainer.User{ID: 1, Role: portainer.AdministratorRole},
	}))

	rr := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr, r)
	require.Nil(t, herr)
	require.Equal(t, http.StatusOK, rr.Code)

	var tmpl portainer.CustomTemplate
	require.NoError(t, json.NewDecoder(rr.Body).Decode(&tmpl))
	require.Equal(t, "K8s Upload Template", tmpl.Title)
	require.Equal(t, filesystem.ComposeFileDefaultName, tmpl.EntryPoint)

	err = ds.ViewTx(func(tx dataservices.DataStoreTx) error {
		templates, err := tx.CustomTemplate().ReadAll()
		require.NoError(t, err)
		require.Len(t, templates, 1)

		return nil
	})
	require.NoError(t, err)
}

func TestCustomTemplateCreate_FromFileUpload_Variables(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)

	vars, err := json.Marshal([]portainer.CustomTemplateVariableDefinition{{Name: "IMAGE", Label: "Docker image"}})
	require.NoError(t, err)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	err = writer.WriteField("Title", "Template With Variables")
	require.NoError(t, err)

	err = writer.WriteField("Description", "A description")
	require.NoError(t, err)

	err = writer.WriteField("Type", "2")
	require.NoError(t, err)

	err = writer.WriteField("Platform", "1")
	require.NoError(t, err)

	err = writer.WriteField("Variables", string(vars))
	require.NoError(t, err)

	part, err := writer.CreateFormFile("File", "docker-compose.yml")
	require.NoError(t, err)

	_, err = part.Write([]byte("version: '3'"))
	require.NoError(t, err)

	require.NoError(t, writer.Close())

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/file", &body)
	r.Header.Set("Content-Type", writer.FormDataContentType())
	r = mux.SetURLVars(r, map[string]string{"method": "file"})
	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: 1, Role: portainer.AdministratorRole})
	r = r.WithContext(ctx)
	r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  1,
		IsAdmin: true,
		User:    &portainer.User{ID: 1, Role: portainer.AdministratorRole},
	}))

	rr := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr, r)
	require.Nil(t, herr)
	require.Equal(t, http.StatusOK, rr.Code)

	var tmpl portainer.CustomTemplate
	require.NoError(t, json.NewDecoder(rr.Body).Decode(&tmpl))
	require.Len(t, tmpl.Variables, 1)
	require.Equal(t, "IMAGE", tmpl.Variables[0].Name)

	err = ds.ViewTx(func(tx dataservices.DataStoreTx) error {
		stored, err := tx.CustomTemplate().Read(tmpl.ID)
		require.NoError(t, err)
		require.Len(t, stored.Variables, 1)

		return nil
	})
	require.NoError(t, err)
}

func TestCustomTemplateCreate_FromFileUpload_InvalidVariables(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)

	err := writer.WriteField("Title", "My Template")
	require.NoError(t, err)

	err = writer.WriteField("Description", "A description")
	require.NoError(t, err)

	err = writer.WriteField("Type", "2")
	require.NoError(t, err)

	err = writer.WriteField("Platform", "1")
	require.NoError(t, err)

	err = writer.WriteField("Variables", "not-valid-json")
	require.NoError(t, err)

	part, err := writer.CreateFormFile("File", "docker-compose.yml")
	require.NoError(t, err)

	_, err = part.Write([]byte("version: '3'"))
	require.NoError(t, err)

	require.NoError(t, writer.Close())

	r := httptest.NewRequest(http.MethodPost, "/custom_templates/create/file", &body)
	r.Header.Set("Content-Type", writer.FormDataContentType())
	r = mux.SetURLVars(r, map[string]string{"method": "file"})
	ctx := security.StoreTokenData(r, &portainer.TokenData{ID: 1, Role: portainer.AdministratorRole})
	r = r.WithContext(ctx)
	r = r.WithContext(security.StoreRestrictedRequestContext(r, &security.RestrictedRequestContext{
		UserID:  1,
		IsAdmin: true,
		User:    &portainer.User{ID: 1, Role: portainer.AdministratorRole},
	}))

	rr := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

type gitServiceCreatingFile struct {
	portainer.GitService
}

func (g *gitServiceCreatingFile) CloneRepository(_ context.Context, destination, _, _, _, _ string, _ bool) error {
	if err := os.MkdirAll(destination, 0700); err != nil {
		return err
	}

	f, err := os.Create(filesystem.JoinPaths(destination, filesystem.ComposeFileDefaultName))
	if err != nil {
		return err
	}

	return f.Close()
}

func (g *gitServiceCreatingFile) LatestCommitID(_ context.Context, _, _, _, _ string, _ bool) (string, error) {
	return "deadbeef123", nil
}

func TestCustomTemplateCreate_FromRepository_Success(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)
	handler.GitService = &gitServiceCreatingFile{}

	payload := customTemplateFromGitRepositoryPayload{
		Title:         "Git Template",
		Description:   "Created from git",
		RepositoryURL: "https://github.com/example/repo",
		Type:          portainer.DockerComposeStack,
		Platform:      portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "repository", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.Nil(t, herr)
	require.Equal(t, http.StatusOK, rr.Code)

	var tmpl portainer.CustomTemplate
	require.NoError(t, json.NewDecoder(rr.Body).Decode(&tmpl))
	require.Equal(t, "Git Template", tmpl.Title)
	require.NotNil(t, tmpl.Artifact)
	require.Len(t, tmpl.Artifact.Files, 1)
	require.Equal(t, "deadbeef123", tmpl.Artifact.Files[0].Hash)

	err := ds.ViewTx(func(tx dataservices.DataStoreTx) error {
		stored, err := tx.CustomTemplate().Read(tmpl.ID)
		require.NoError(t, err)
		require.NotNil(t, stored.Artifact)

		src, err := tx.Source().Read(adminUserContext, stored.Artifact.Files[0].SourceID)
		require.NoError(t, err)
		require.Equal(t, portainer.SourceTypeGit, src.Type)
		require.Equal(t, "https://github.com/example/repo", src.Git.URL)

		return nil
	})
	require.NoError(t, err)
}

func TestCustomTemplateCreate_FromRepository_DeduplicatesSource(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)
	handler.GitService = &gitServiceCreatingFile{}

	makePayload := func(title string) customTemplateFromGitRepositoryPayload {
		return customTemplateFromGitRepositoryPayload{
			Title:         title,
			Description:   "Created from git",
			RepositoryURL: "https://github.com/example/repo",
			Type:          portainer.DockerComposeStack,
			Platform:      portainer.CustomTemplatePlatformLinux,
		}
	}

	r1 := createTemplateRequest(t, "repository", makePayload("Template One"), 1, portainer.AdministratorRole)
	rr1 := httptest.NewRecorder()
	herr := handler.customTemplateCreate(rr1, r1)
	require.Nil(t, herr)

	r2 := createTemplateRequest(t, "repository", makePayload("Template Two"), 1, portainer.AdministratorRole)
	rr2 := httptest.NewRecorder()
	herr = handler.customTemplateCreate(rr2, r2)
	require.Nil(t, herr)

	err := ds.ViewTx(func(tx dataservices.DataStoreTx) error {
		sources, err := tx.Source().ReadAll(adminUserContext)
		require.NoError(t, err)
		require.Len(t, sources, 1, "two templates with the same URL must share one Source")

		return nil
	})
	require.NoError(t, err)
}

func TestCustomTemplateCreate_FromRepository_Validation_MissingTitle(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromGitRepositoryPayload{
		Description:   "A description",
		RepositoryURL: "https://github.com/example/repo",
		Type:          portainer.DockerComposeStack,
		Platform:      portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "repository", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromRepository_Validation_MissingDescription(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromGitRepositoryPayload{
		Title:         "My Template",
		RepositoryURL: "https://github.com/example/repo",
		Type:          portainer.DockerComposeStack,
		Platform:      portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "repository", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromRepository_Validation_InvalidURL(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromGitRepositoryPayload{
		Title:         "My Template",
		Description:   "A description",
		RepositoryURL: "http://",
		Type:          portainer.DockerComposeStack,
		Platform:      portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "repository", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromRepository_Validation_AuthWithoutCredentials(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromGitRepositoryPayload{
		Title:                    "My Template",
		Description:              "A description",
		RepositoryURL:            "https://github.com/example/repo",
		RepositoryAuthentication: true,
		Type:                     portainer.DockerComposeStack,
		Platform:                 portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "repository", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromRepository_Validation_InvalidPlatform(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromGitRepositoryPayload{
		Title:         "My Template",
		Description:   "A description",
		RepositoryURL: "https://github.com/example/repo",
		Type:          portainer.DockerComposeStack,
		Platform:      0,
	}

	r := createTemplateRequest(t, "repository", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromRepository_Validation_InvalidType(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)

	payload := customTemplateFromGitRepositoryPayload{
		Title:         "My Template",
		Description:   "A description",
		RepositoryURL: "https://github.com/example/repo",
		Type:          0,
		Platform:      portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "repository", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}

func TestCustomTemplateCreate_FromRepository_WithSourceID_Success(t *testing.T) {
	t.Parallel()

	handler, ds, _ := newTestHandler(t)
	handler.GitService = &gitServiceCreatingFile{}

	var srcID portainer.SourceID
	require.NoError(t, ds.UpdateTx(func(tx dataservices.DataStoreTx) error {
		src := &portainer.Source{
			Name: "example/repo",
			Type: portainer.SourceTypeGit,
			Git: &gittypes.GitSource{
				URL: "https://github.com/example/repo",
			},
		}
		err := tx.Source().Create(adminUserContext, src)
		require.NoError(t, err)
		srcID = src.ID
		return nil
	}))

	payload := customTemplateFromGitRepositoryPayload{
		Title:       "Source Template",
		Description: "Created from source ID",
		SourceID:    srcID,
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "repository", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.Nil(t, herr)
	require.Equal(t, http.StatusOK, rr.Code)

	var tmpl portainer.CustomTemplate
	require.NoError(t, json.NewDecoder(rr.Body).Decode(&tmpl))
	require.NotNil(t, tmpl.Artifact)
	require.Len(t, tmpl.Artifact.Files, 1)
	require.Equal(t, srcID, tmpl.Artifact.Files[0].SourceID)
	require.Equal(t, "deadbeef123", tmpl.Artifact.Files[0].Hash)
}

func TestCustomTemplateCreate_FromRepository_WithSourceID_NonExistentSource(t *testing.T) {
	t.Parallel()

	handler, _, _ := newTestHandler(t)
	handler.GitService = &gitServiceCreatingFile{}

	payload := customTemplateFromGitRepositoryPayload{
		Title:       "Source Template",
		Description: "Created from non-existent source ID",
		SourceID:    999,
		Type:        portainer.DockerComposeStack,
		Platform:    portainer.CustomTemplatePlatformLinux,
	}

	r := createTemplateRequest(t, "repository", payload, 1, portainer.AdministratorRole)
	rr := httptest.NewRecorder()

	herr := handler.customTemplateCreate(rr, r)
	require.NotNil(t, herr)
	require.Equal(t, http.StatusInternalServerError, herr.StatusCode)
}
