package analytics_api_test

// Docs:
// - Folder Collections (POST/GET): https://developer.salesforce.com/docs/atlas.en-us.254.0.api_analytics.meta/api_analytics/analytics_api_folders_create.htm
// - Folder Operations (GET/PATCH/DELETE): https://developer.salesforce.com/docs/atlas.en-us.254.0.api_analytics.meta/api_analytics/analytics_api_folders_ops.htm

import (
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"testing"

	"github.com/nexuscrm/tests/contract/analytics_api/internal/arrange"
	"github.com/nexuscrm/tests/contract/internal/contracttest"
	"github.com/nexuscrm/tests/contract/internal/liveapi"
	"github.com/stretchr/testify/require"
)

func TestAnalyticsAPI_FolderCRUD(t *testing.T) {
	cfg := liveapi.LoadConfig(t)
	client := liveapi.NewAuthenticatedClient(t)

	t.Run("unauthorized requests reject missing bearer token", func(t *testing.T) {
		cases := []struct {
			name   string
			method string
			path   string
			body   any
		}{
			{
				name:   "POST /folders",
				method: http.MethodPost,
				path:   "/folders",
				body: map[string]any{
					"label": arrange.UniqueName("unauthorized-folder"),
					"name":  "unauthorized_folder",
					"type":  "report",
				},
			},
			{
				name:   "GET /folders/{id}",
				method: http.MethodGet,
				path:   "/folders/00l000000000000AAA",
			},
			{
				name:   "PATCH /folders/{id}",
				method: http.MethodPatch,
				path:   "/folders/00l000000000000AAA",
				body: map[string]any{
					"label": "unauthorized-update",
				},
			},
		}

		for _, tc := range cases {
			t.Run(tc.name, func(t *testing.T) {
				resp := contracttest.DoAnonymousRequest(t, tc.method, tc.path, tc.body)
				contracttest.AssertSalesforceError(t, resp, http.StatusUnauthorized, "INVALID_SESSION_ID")
			})
		}
	})

	t.Run("create folder validation errors", func(t *testing.T) {
		cases := []struct {
			name       string
			body       any
			wantStatus int
			wantCode   string
		}{
			{
				name:       "missing required fields",
				body:       map[string]any{},
				wantStatus: http.StatusBadRequest,
				wantCode:   "REQUIRED_FIELD_MISSING",
			},
			{
				name: "missing label",
				body: map[string]any{
					"name": sanitizeFolderName(arrange.UniqueName("folder")),
					"type": "report",
				},
				wantStatus: http.StatusBadRequest,
				wantCode:   "REQUIRED_FIELD_MISSING",
			},
			{
				name: "missing type",
				body: map[string]any{
					"label": arrange.UniqueName("folder"),
					"name":  sanitizeFolderName(arrange.UniqueName("folder")),
				},
				wantStatus: http.StatusBadRequest,
				wantCode:   "REQUIRED_FIELD_MISSING",
			},
		}

		for _, tc := range cases {
			t.Run(tc.name, func(t *testing.T) {
				resp, err := client.DoJSON(http.MethodPost, "/folders", tc.body)
				require.NoError(t, err)
				contracttest.AssertSalesforceError(t, resp, tc.wantStatus, tc.wantCode)
			})
		}
	})

	label := arrange.UniqueName("contract-folder")
	name := sanitizeFolderName(arrange.UniqueName("contract_folder"))

	createReq := map[string]any{
		"label": label,
		"name":  name,
		// Salesforce examples serialize this as `report` / `dashboard` (lowercase).
		"type": "report",
	}

	createResp, err := client.DoJSON(http.MethodPost, "/folders", createReq)
	require.NoError(t, err)
	require.Contains(t, []int{http.StatusCreated, http.StatusOK}, createResp.StatusCode)
	contracttest.AssertJSONContentType(t, createResp)

	var createBody map[string]any
	liveapi.DecodeJSON(t, createResp, &createBody)
	folderID, _ := createBody["id"].(string)
	if folderID == "" {
		folderID, _ = createBody["Id"].(string)
	}
	require.NotEmpty(t, folderID, "create folder should return id")
	contracttest.AssertSalesforceID(t, folderID)

	t.Cleanup(func() {
		resp, err := client.Do(http.MethodDelete, "/folders/"+url.PathEscape(folderID), "", nil)
		if err == nil {
			_ = resp.Body.Close()
		}
	})

	t.Run("get created folder returns folder detail payload", func(t *testing.T) {
		resp, err := client.DoJSON(http.MethodGet, "/folders/"+url.PathEscape(folderID), nil)
		require.NoError(t, err)
		require.Equal(t, http.StatusOK, resp.StatusCode)
		contracttest.AssertJSONContentType(t, resp)

		var body map[string]any
		liveapi.DecodeJSON(t, resp, &body)

		require.Equal(t, folderID, body["id"])
		require.NotEmpty(t, body["label"])
		require.NotEmpty(t, body["name"])
	})

	t.Run("patch updates folder label and get reflects the change", func(t *testing.T) {
		updatedLabel := arrange.UniqueName("updated-folder")

		resp, err := client.DoJSON(http.MethodPatch, "/folders/"+url.PathEscape(folderID), map[string]any{
			"label": updatedLabel,
		})
		require.NoError(t, err)
		require.Equal(t, http.StatusOK, resp.StatusCode)
		contracttest.AssertJSONContentType(t, resp)
		_ = resp.Body.Close()

		getResp, err := client.DoJSON(http.MethodGet, "/folders/"+url.PathEscape(folderID), nil)
		require.NoError(t, err)
		require.Equal(t, http.StatusOK, getResp.StatusCode)

		var body map[string]any
		liveapi.DecodeJSON(t, getResp, &body)
		if got, ok := body["label"].(string); ok && got != "" {
			require.Equal(t, updatedLabel, got)
		}
	})

	t.Run("not found folder IDs surface target specific errors", func(t *testing.T) {
		missingID := "00l000000000000AAA"
		paths := []struct {
			method string
			path   string
			body   any
		}{
			{method: http.MethodGet, path: "/folders/" + missingID},
			{method: http.MethodPatch, path: "/folders/" + missingID, body: map[string]any{"label": "missing"}},
		}

		for _, tc := range paths {
			t.Run(tc.method+" "+tc.path, func(t *testing.T) {
				resp, err := client.DoJSON(tc.method, tc.path, tc.body)
				require.NoError(t, err)

				if cfg.TargetName == "sandbox" {
					arrange.RequireSalesforceErrorOneOf(t, resp, http.StatusNotFound)
					return
				}

				require.Contains(t, []int{http.StatusNotFound, http.StatusInternalServerError}, resp.StatusCode)
				_ = resp.Body.Close()
			})
		}
	})
}

var folderNameSanitizer = regexp.MustCompile(`[^A-Za-z0-9_]+`)

func sanitizeFolderName(raw string) string {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return "Contract_Folder"
	}
	s := folderNameSanitizer.ReplaceAllString(trimmed, "_")
	s = strings.Trim(s, "_")
	if s == "" {
		return "Contract_Folder"
	}
	return s
}
