package scenarios_test

// Scenario tests for Flow Builder boolean constant handling.
//
// FLOW-000: Baseline — verify the flow engine can create, activate, and
//           execute a trivial string-assignment flow.
// FLOW-001: $GlobalConstant.True/False values are correctly resolved
//           during flow execution, not stored as null/raw reference.

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

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

// ---------- FLOW-000: Baseline flow execution ----------

func TestFLOW000_BasicFlowExecution(t *testing.T) {
	// Scenario: Create and execute a trivial flow that assigns a string literal
	// to an output variable. This validates the flow engine itself works,
	// isolating $GlobalConstant issues from engine-level problems.

	client := liveapi.NewAuthenticatedClient(t)
	// Avoid underscores — the backend strips them from the developer name,
	// causing lookup mismatches at execution time.
	devName := fmt.Sprintf("BaselineFlow%d", time.Now().UnixNano())

	flowPayload := map[string]any{
		"FullName":    devName,
		"Status":      "Draft",
		"ProcessType": "AutoLaunchedFlow",
		"Metadata": map[string]any{
			"label":      devName,
			"apiVersion": "63.0",
			"status":     "Draft",
			"start": map[string]any{
				"connector": map[string]any{
					"targetReference": "SetString",
				},
			},
			"variables": []map[string]any{
				{
					"name":     "OutputText",
					"dataType": "String",
					"isOutput": true,
				},
			},
			"assignments": []map[string]any{
				{
					"name": "SetString",
					"assignmentItems": []map[string]any{
						{
							"assignToReference": "OutputText",
							"operator":          "Assign",
							"value": map[string]any{
								"stringValue": "hello",
							},
						},
					},
				},
			},
		},
	}

	// Create the flow
	createResp, err := client.DoJSON(http.MethodPost, "/tooling/sobjects/Flow", flowPayload)
	require.NoError(t, err)

	if createResp.StatusCode != http.StatusCreated {
		body := liveapi.ReadBody(t, createResp)
		t.Fatalf("could not create test flow (status %d): %s", createResp.StatusCode, string(body))
	}

	var createBody struct {
		ID      string `json:"id"`
		Success bool   `json:"success"`
	}
	liveapi.DecodeJSON(t, createResp, &createBody)
	require.True(t, createBody.Success)
	require.NotEmpty(t, createBody.ID)

	flowID := createBody.ID
	t.Cleanup(func() {
		delResp, delErr := client.Do(http.MethodDelete, "/tooling/sobjects/Flow/"+flowID, "", nil)
		if delErr == nil {
			delResp.Body.Close()
		}
	})

	// Activate the flow
	activateResp, err := client.DoJSON(http.MethodPatch, "/tooling/sobjects/Flow/"+flowID, map[string]any{
		"Status": "Active",
	})
	require.NoError(t, err)
	if activateResp.StatusCode != http.StatusNoContent {
		body := liveapi.ReadBody(t, activateResp)
		t.Fatalf("could not activate flow (status %d): %s", activateResp.StatusCode, string(body))
	}
	activateResp.Body.Close()

	// Execute the flow
	execResp, err := client.DoJSON(http.MethodPost,
		"/actions/custom/flow/"+devName,
		map[string]any{
			"inputs": []map[string]any{{}},
		})
	require.NoError(t, err)

	// Read and log full response body before any assertions.
	execBodyRaw, err := io.ReadAll(execResp.Body)
	execResp.Body.Close()
	require.NoError(t, err)
	t.Logf("flow execution response (status %d): %s", execResp.StatusCode, string(execBodyRaw))

	require.Equal(t, http.StatusOK, execResp.StatusCode,
		"flow execution HTTP status should be 200")

	var execBody []map[string]any
	require.NoError(t, json.Unmarshal(execBodyRaw, &execBody))
	require.NotEmpty(t, execBody, "expected at least one action result")

	result := execBody[0]
	assert.Equal(t, true, result["isSuccess"], "baseline flow execution should succeed")

	outputValues, ok := result["outputValues"].(map[string]any)
	require.True(t, ok, "outputValues should be a map")

	// The output might be a plain string or a typed-value object.
	switch v := outputValues["OutputText"].(type) {
	case string:
		assert.Equal(t, "hello", v)
	case map[string]any:
		assert.Equal(t, "hello", v["stringValue"],
			"OutputText stringValue should be 'hello'")
	default:
		t.Errorf("unexpected OutputText type %T: %v", v, v)
	}
}

// ---------- FLOW-001: $GlobalConstant.True resolves to boolean true ----------

func TestFLOW001_GlobalConstantTrue(t *testing.T) {
	// Scenario: Create a Flow with an Assignment node that sets a variable
	// to $GlobalConstant.True. Execute the flow and verify the output
	// variable is boolean true, not null or a raw element reference.

	client := liveapi.NewAuthenticatedClient(t)
	// Avoid underscores — the backend strips them from the developer name,
	// causing lookup mismatches at execution time.
	devName := fmt.Sprintf("BoolTest%d", time.Now().UnixNano())

	// Arrange: create a flow that assigns $GlobalConstant.True to an output variable.
	flowPayload := map[string]any{
		"FullName":    devName,
		"Status":      "Draft",
		"ProcessType": "AutoLaunchedFlow",
		"Metadata": map[string]any{
			"label":      devName,
			"apiVersion": "63.0",
			"status":     "Draft",
			"start": map[string]any{
				"connector": map[string]any{
					"targetReference": "SetBoolean",
				},
			},
			"variables": []map[string]any{
				{
					"name":     "OutputBool",
					"dataType": "Boolean",
					"isOutput": true,
				},
			},
			"assignments": []map[string]any{
				{
					"name": "SetBoolean",
					"assignmentItems": []map[string]any{
						{
							"assignToReference": "OutputBool",
							"operator":          "Assign",
							"value": map[string]any{
								"elementReference": "$GlobalConstant.True",
							},
						},
					},
				},
			},
		},
	}

	// Create the flow
	createResp, err := client.DoJSON(http.MethodPost, "/tooling/sobjects/Flow", flowPayload)
	require.NoError(t, err)

	if createResp.StatusCode != http.StatusCreated {
		body := liveapi.ReadBody(t, createResp)
		t.Fatalf("could not create test flow (status %d): %s", createResp.StatusCode, string(body))
	}

	var createBody struct {
		ID      string `json:"id"`
		Success bool   `json:"success"`
	}
	liveapi.DecodeJSON(t, createResp, &createBody)
	require.True(t, createBody.Success)
	require.NotEmpty(t, createBody.ID)

	flowID := createBody.ID
	t.Cleanup(func() {
		delResp, delErr := client.Do(http.MethodDelete, "/tooling/sobjects/Flow/"+flowID, "", nil)
		if delErr == nil {
			delResp.Body.Close()
		}
	})

	// Activate the flow
	activateResp, err := client.DoJSON(http.MethodPatch, "/tooling/sobjects/Flow/"+flowID, map[string]any{
		"Status": "Active",
	})
	require.NoError(t, err)
	if activateResp.StatusCode != http.StatusNoContent {
		body := liveapi.ReadBody(t, activateResp)
		t.Fatalf("could not activate flow (status %d): %s", activateResp.StatusCode, string(body))
	}
	activateResp.Body.Close()

	// Act: execute the flow
	execResp, err := client.DoJSON(http.MethodPost,
		"/actions/custom/flow/"+devName,
		map[string]any{
			"inputs": []map[string]any{{}},
		})
	require.NoError(t, err)

	// Read and log full response body before any assertions so we can see
	// the actual backend output when debugging failures.
	execBodyRaw, err := io.ReadAll(execResp.Body)
	execResp.Body.Close()
	require.NoError(t, err)
	t.Logf("flow execution response (status %d): %s", execResp.StatusCode, string(execBodyRaw))

	require.Equal(t, http.StatusOK, execResp.StatusCode,
		"flow execution HTTP status should be 200")

	// Assert: OutputBool should be true, not null
	var execBody []map[string]any
	require.NoError(t, json.Unmarshal(execBodyRaw, &execBody))
	require.NotEmpty(t, execBody, "expected at least one action result")

	result := execBody[0]

	// Log errors if present, regardless of isSuccess value.
	if errs, ok := result["errors"]; ok {
		t.Logf("flow execution errors: %v", errs)
	}
	if errMsg, ok := result["errorMessage"]; ok {
		t.Logf("flow execution errorMessage: %v", errMsg)
	}

	assert.Equal(t, true, result["isSuccess"], "flow execution should succeed")

	outputValues, ok := result["outputValues"].(map[string]any)
	require.True(t, ok, "outputValues should be a map")

	outputBool := outputValues["OutputBool"]
	t.Logf("OutputBool raw value: %#v", outputBool)

	assert.NotNil(t, outputBool,
		"OutputBool should not be null — $GlobalConstant.True must resolve to true")

	// The backend may return a raw boolean true, or a typed-value object.
	// If it returns the unresolved reference {"elementReference":"$GlobalConstant.True"},
	// that is the bug this test is designed to catch.
	switch v := outputBool.(type) {
	case bool:
		assert.True(t, v, "OutputBool should be boolean true")
	case map[string]any:
		// If it's a typed-value wrapper like {"booleanValue": true}, accept that.
		if bv, hasBool := v["booleanValue"]; hasBool {
			assert.Equal(t, true, bv, "OutputBool booleanValue should be true")
		} else if ref, hasRef := v["elementReference"]; hasRef {
			// This is the known bug: $GlobalConstant.True was not resolved.
			t.Errorf("$GlobalConstant.True was NOT resolved — returned as raw reference: %v", ref)
		} else {
			t.Errorf("unexpected OutputBool map value: %v", v)
		}
	default:
		t.Errorf("unexpected OutputBool type %T: %v", v, v)
	}
}
