package automation

import (
	"testing"

	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestContractFlowExecutionResultFromModelCopiesCarrier(t *testing.T) {
	source := &models.FlowExecutionResult{
		ActionName:    "MyFlow",
		IsSuccess:     false,
		IsCustomError: true,
		OutputValues: map[string]interface{}{
			"ok": true,
			"record": models.SObject{
				"attributes": map[string]interface{}{"type": "Account"},
				"Id":         "001",
				"Owner": models.SObject{
					"attributes": map[string]interface{}{"type": "User"},
					"Id":         "005",
				},
			},
			"plain": map[string]interface{}{
				"nested": map[string]interface{}{"value": "kept"},
			},
			"alreadyNeutral": contract.Record{
				models.ObjectTypeMetadataKey: "Case",
				"Id":                         "500",
			},
			"recordList": []models.SObject{{
				"attributes": map[string]interface{}{"type": "Contact"},
				"Id":         "003",
			}},
		},
		Errors: []models.FlowExecutionError{{
			StatusCode: contract.FlowExecutionErrorCodeFieldWriteDenied,
			Message:    "failed",
			Fields:     []string{"LeadId"},
		}},
	}

	got := contractFlowExecutionResultFromModel(source)
	if got == nil {
		t.Fatal("contractFlowExecutionResultFromModel() = nil")
	}
	if got.ActionName != "MyFlow" || got.IsSuccess || !got.IsCustomError {
		t.Fatalf("result header = %#v", got)
	}
	if got.OutputValues["ok"] != true {
		t.Fatalf("OutputValues = %#v, want ok=true", got.OutputValues)
	}
	record, ok := got.OutputValues["record"].(contract.Record)
	if !ok {
		t.Fatalf("record output = %T %#v, want contract.Record", got.OutputValues["record"], got.OutputValues["record"])
	}
	if record[models.ObjectTypeMetadataKey] != "Account" {
		t.Fatalf("record object type = %#v, want Account", record[models.ObjectTypeMetadataKey])
	}
	if _, ok := record["attributes"]; ok {
		t.Fatalf("record output leaked v63 attributes: %#v", record)
	}
	owner, ok := record["Owner"].(contract.Record)
	if !ok {
		t.Fatalf("owner output = %T %#v, want contract.Record", record["Owner"], record["Owner"])
	}
	if owner[models.ObjectTypeMetadataKey] != "User" {
		t.Fatalf("owner object type = %#v, want User", owner[models.ObjectTypeMetadataKey])
	}
	alreadyNeutral, ok := got.OutputValues["alreadyNeutral"].(contract.Record)
	if !ok {
		t.Fatalf("alreadyNeutral output = %T %#v, want contract.Record", got.OutputValues["alreadyNeutral"], got.OutputValues["alreadyNeutral"])
	}
	if alreadyNeutral[models.ObjectTypeMetadataKey] != "Case" || alreadyNeutral["Id"] != "500" {
		t.Fatalf("alreadyNeutral output = %#v, want neutral Case record", alreadyNeutral)
	}
	recordList, ok := got.OutputValues["recordList"].([]contract.Record)
	if !ok || len(recordList) != 1 {
		t.Fatalf("recordList output = %T %#v, want one neutral record", got.OutputValues["recordList"], got.OutputValues["recordList"])
	}
	if recordList[0][models.ObjectTypeMetadataKey] != "Contact" || recordList[0]["Id"] != "003" {
		t.Fatalf("recordList[0] = %#v, want neutral Contact record", recordList[0])
	}
	if len(got.Errors) != 1 || got.Errors[0].Code != contract.FlowExecutionErrorCodeFieldWriteDenied || got.Errors[0].Fields[0] != "LeadId" {
		t.Fatalf("Errors = %#v, want copied error code and fields", got.Errors)
	}

	source.OutputValues["ok"] = false
	source.OutputValues["record"].(models.SObject)["Id"] = "mutated"
	source.OutputValues["record"].(models.SObject)["Owner"].(models.SObject)["Id"] = "mutated"
	source.OutputValues["plain"].(map[string]interface{})["nested"].(map[string]interface{})["value"] = "mutated"
	source.OutputValues["alreadyNeutral"].(contract.Record)["Id"] = "mutated"
	source.OutputValues["recordList"].([]models.SObject)[0]["Id"] = "mutated"
	source.Errors[0].Fields[0] = "mutated"
	if got.OutputValues["ok"] != true {
		t.Fatalf("OutputValues aliased model carrier: %#v", got.OutputValues)
	}
	if record["Id"] != "001" || owner["Id"] != "005" {
		t.Fatalf("record output aliased model carrier: record=%#v owner=%#v", record, owner)
	}
	if alreadyNeutral["Id"] != "500" || recordList[0]["Id"] != "003" {
		t.Fatalf("record outputs aliased model carrier: alreadyNeutral=%#v recordList=%#v", alreadyNeutral, recordList)
	}
	plain := got.OutputValues["plain"].(map[string]any)
	nested := plain["nested"].(map[string]any)
	if nested["value"] != "kept" {
		t.Fatalf("plain map output aliased model carrier: %#v", plain)
	}
	if got.Errors[0].Fields[0] != "LeadId" {
		t.Fatalf("Errors aliased model carrier: %#v", got.Errors)
	}
}

// A plain JSON flow-output value whose nested object carries an "attributes" map
// WITH a "type" key must pass through as data, not be misread as a neutral record
// (which would drop the user's payload). Regression guard for the shared
// record/plain-map discriminator (models.IsNeutralRecordMap): the conversion must
// gate on _objectType, never on RecordObjectType's attributes.type fallback.
func TestContractFlowOutputValueFromModelPreservesPlainJSONWithAttributesType(t *testing.T) {
	out := contractFlowOutputValueFromModel(map[string]interface{}{
		"attributes": map[string]interface{}{"type": "external", "source": "x"},
		"value":      float64(1),
	})

	m, ok := out.(map[string]any)
	if !ok {
		t.Fatalf("plain JSON flow output = %T %#v, want map (not converted to a record)", out, out)
	}
	attrs, ok := m["attributes"].(map[string]any)
	if !ok {
		t.Fatalf("flow-output JSON \"attributes\" rewritten/dropped: %#v", m)
	}
	if attrs["type"] != "external" || attrs["source"] != "x" {
		t.Fatalf("flow-output JSON attributes corrupted: %#v", attrs)
	}
	if m["value"] != float64(1) {
		t.Fatalf("flow-output JSON value corrupted: %#v", m["value"])
	}
}

// A genuine record (carries _objectType) is still converted to a neutral record.
func TestContractFlowOutputValueFromModelConvertsObjectTypeStampedMap(t *testing.T) {
	out := contractFlowOutputValueFromModel(map[string]interface{}{
		models.ObjectTypeMetadataKey: "Account",
		"Id":                         "001000000000001AAA",
	})

	rec, ok := out.(contract.Record)
	if !ok {
		t.Fatalf("stamped record output = %T %#v, want contract.Record", out, out)
	}
	if rec[models.ObjectTypeMetadataKey] != "Account" || rec["Id"] != "001000000000001AAA" {
		t.Fatalf("converted record = %#v, want neutral Account record", rec)
	}
}

func TestContractFlowExecutionResultFromModelPreservesNil(t *testing.T) {
	if got := contractFlowExecutionResultFromModel(nil); got != nil {
		t.Fatalf("contractFlowExecutionResultFromModel(nil) = %#v, want nil", got)
	}
	if got := contractFlowExecutionErrorsFromModel(nil); got != nil {
		t.Fatalf("contractFlowExecutionErrorsFromModel(nil) = %#v, want nil", got)
	}
	if got := contractFlowOutputValuesFromModel(nil); got != nil {
		t.Fatalf("contractFlowOutputValuesFromModel(nil) = %#v, want nil", got)
	}
}
