package scenarios_test

// Scenario tests for role-based permissions.
//
// These verify that non-admin users (Sales profile) can authenticate,
// perform CRUD on business objects, and are restricted from system tables.

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/nexuscrm/tests/contract/internal/liveapi"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// salesClient returns an authenticated client for the Sales test user.
// Skips the test if the Sales user does not exist or cannot authenticate.
func salesClient(t testing.TB) *liveapi.Client {
	t.Helper()

	cfg := liveapi.LoadConfig(t)

	// Authenticate as sales@test.com via password grant
	form := url.Values{}
	form.Set("grant_type", "password")
	form.Set("username", "sales@test.com")
	form.Set("password", "Sales123!")

	req, err := http.NewRequest(http.MethodPost, cfg.TokenURL,
		strings.NewReader(form.Encode()))
	require.NoError(t, err)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	httpClient := &http.Client{Timeout: 30 * time.Second}
	resp, err := httpClient.Do(req)
	require.NoError(t, err)
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		t.Skipf("Sales user (sales@test.com) not available — status %d: %s",
			resp.StatusCode, strings.TrimSpace(string(body)))
	}

	var tokenResp struct {
		AccessToken string `json:"access_token"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
		t.Fatalf("decode sales token response: %v", err)
	}
	if tokenResp.AccessToken == "" {
		t.Skip("Sales user token response had empty access_token")
	}

	return liveapi.NewClientWithToken(cfg, tokenResp.AccessToken)
}

// ---------- ROLE-001: Sales user can create business records ----------

func TestROLE001_SalesUserCRUD(t *testing.T) {
	// Scenario: Login as sales@test.com (Sales profile). Create an Account
	// record. This verifies that the Sales user has basic CRUD permissions
	// on standard business objects.

	client := salesClient(t)

	acctName := fmt.Sprintf("ROLE001-%d", time.Now().UnixNano())
	resp, err := client.DoJSON(http.MethodPost, "/sobjects/Account", map[string]any{
		"Name":     acctName,
		"Industry": "Technology",
	})
	require.NoError(t, err)

	body := liveapi.ReadBody(t, resp)
	t.Logf("Sales user Account create response (status %d): %s", resp.StatusCode, string(body))

	// Must be 201 (created). Other statuses indicate permission problems:
	// - 403 = permission denied (Sales profile lacks Account CRUD)
	// - 404 = Account object not found
	// - 400 = could be validation rule OR permission issue — ambiguous
	// - 500 = server error
	// We require 201 specifically to prove CRUD works.
	require.Equal(t, http.StatusCreated, resp.StatusCode,
		"Sales user should be able to create Account records (got %d): %s",
		resp.StatusCode, string(body))

	var created struct {
		ID string `json:"id"`
	}
	require.NoError(t, json.Unmarshal(body, &created))
	require.NotEmpty(t, created.ID)
	t.Logf("Sales user created Account %s (id=%s)", acctName, created.ID)

	// Cleanup
	t.Cleanup(func() {
		delResp, _ := client.Do(http.MethodDelete, "/sobjects/Account/"+created.ID, "", nil)
		if delResp != nil {
			delResp.Body.Close()
		}
	})
}

// ---------- ROLE-002: Sales user cannot write to system tables ----------

func TestROLE002_SalesUserSystemTableRestriction(t *testing.T) {
	// Scenario: Sales profile should NOT have write access to _System_*
	// tables. Attempt to POST to a system table and expect 403 or 404.

	client := salesClient(t)

	sysResp, err := client.DoJSON(http.MethodPost, "/sobjects/_System_Config", map[string]any{
		"Name": "test-role002",
	})
	require.NoError(t, err)
	defer sysResp.Body.Close()

	body := liveapi.ReadBody(t, sysResp)
	t.Logf("_System_Config POST as Sales user returned %d: %s", sysResp.StatusCode, string(body))

	// Must NOT be 201 (that means Sales user created a system record — security bug)
	require.NotEqual(t, http.StatusCreated, sysResp.StatusCode,
		"SECURITY: Sales user was able to create _System_Config records")

	// Should be 403 (proper permission enforcement). Other acceptable codes:
	// - 404 if the object is not exposed at all
	// 500 is NOT acceptable — that's a crash, not permission enforcement.
	require.NotEqual(t, http.StatusInternalServerError, sysResp.StatusCode,
		"500 is a server crash, not permission denial — permissions should return 403")

	// Ideally should be exactly 403
	if sysResp.StatusCode != http.StatusForbidden && sysResp.StatusCode != http.StatusNotFound {
		t.Logf("WARNING: expected 403 (Forbidden) or 404 (Not Found), got %d. "+
			"Permission enforcement may not be returning the correct status code.", sysResp.StatusCode)
	}
}

// ---------- ROLE-003: Sales user login verification ----------

func TestROLE003_SalesUserLogin(t *testing.T) {
	// Scenario: Verify that sales@test.com can obtain a token via
	// password grant and that the token is functional.

	client := salesClient(t)

	// Use the authenticated client to hit a lightweight endpoint
	// that confirms the token works. Try /services/data/ (API versions).
	resp, err := client.Do(http.MethodGet, "/services/data/", "", nil)
	require.NoError(t, err)
	defer resp.Body.Close()

	assert.Equal(t, http.StatusOK, resp.StatusCode,
		"Sales user token should be valid for API requests")

	t.Log("Sales user (sales@test.com) authenticated successfully and token is functional")
}
