package approvalir

import (
	"bytes"
	"encoding/json"
	"strings"
	"testing"
)

func publishedDefinitionFixture() *Definition {
	return &Definition{
		Label:  "Review",
		Active: true,
		Policy: ApprovalPolicy{
			RecordEditability: RecordEditabilityAdminOnly,
			AllowedSubmitters: []AllowedSubmitter{{Kind: SubmitterKindAllInternalUsers}},
		},
		Steps: []Step{{
			Name: "Review", Label: "Review", Approvers: []Approver{{Kind: ApproverKindUser, Reference: "user-1"}},
			RejectBehavior: RejectBehaviorFinalRejection, ApprovalActions: []Action{}, RejectionActions: []Action{},
		}},
		InitialSubmissionActions: []Action{}, StepApprovalActions: []Action{}, FinalApprovalActions: []Action{},
		FinalRejectionActions: []Action{}, RecallActions: []Action{}, ReassignActions: []Action{},
	}
}

func TestDefinitionEnvelopeCanonicalRoundTrip(t *testing.T) {
	definition := publishedDefinitionFixture()
	encoded, err := EncodeDefinitionEnvelope([]byte("product-owned source"), definition, nil)
	if err != nil {
		t.Fatalf("EncodeDefinitionEnvelope() error = %v", err)
	}
	decoded, err := DecodeDefinitionEnvelope(encoded, nil)
	if err != nil {
		t.Fatalf("DecodeDefinitionEnvelope() error = %v", err)
	}
	if decoded.Schema != DefinitionEnvelopeSchemaV1 || decoded.Definition.Label != definition.Label ||
		!strings.HasPrefix(decoded.SourceDigest, "sha256:") {
		t.Fatalf("decoded envelope = %#v", decoded)
	}
	reencoded, err := EncodeDefinitionEnvelope([]byte("product-owned source"), &decoded.Definition, nil)
	if err != nil {
		t.Fatalf("EncodeDefinitionEnvelope(round trip) error = %v", err)
	}
	if !bytes.Equal(encoded, reencoded) {
		t.Fatalf("round-trip bytes drifted:\nfirst  = %s\nsecond = %s", encoded, reencoded)
	}
	if bytes.Contains(encoded, []byte(`"steps":null`)) || bytes.Contains(encoded, []byte(`"reassignActions":null`)) {
		t.Fatalf("canonical envelope retained nil collection: %s", encoded)
	}
}

func TestDefinitionEnvelopeFailsClosed(t *testing.T) {
	encoded, err := EncodeDefinitionEnvelope([]byte("source"), publishedDefinitionFixture(), nil)
	if err != nil {
		t.Fatalf("EncodeDefinitionEnvelope() error = %v", err)
	}
	tests := [][]byte{
		append([]byte(" "), encoded...),
		append(append([]byte(nil), encoded...), []byte("{}")...),
		bytes.Replace(encoded, []byte(`"schema":"nexuscrm.approval-definition/v1"`), []byte(`"schema":"other"`), 1),
		bytes.Replace(encoded, []byte(`"sourceDigest":"sha256:`), []byte(`"unknown":true,"sourceDigest":"sha256:`), 1),
	}
	for index, value := range tests {
		if _, err := DecodeDefinitionEnvelope(value, nil); err == nil {
			t.Fatalf("malformed envelope %d was accepted: %s", index, value)
		}
	}
}

func TestDefinitionEnvelopeUsesPlatformFormulaValidator(t *testing.T) {
	definition := publishedDefinitionFixture()
	definition.EntryCriteria = &Criteria{Formula: "Amount > 10"}
	validator := FormulaValidatorFunc(func(source string) error {
		if source != "Amount > 10" {
			t.Fatalf("formula source = %q", source)
		}
		return nil
	})
	encoded, err := EncodeDefinitionEnvelope([]byte("source"), definition, validator)
	if err != nil {
		t.Fatalf("EncodeDefinitionEnvelope() error = %v", err)
	}
	if _, err := DecodeDefinitionEnvelope(encoded, nil); err == nil {
		t.Fatal("formula definition was accepted without the platform formula validator")
	}
	if _, err := DecodeDefinitionEnvelope(encoded, validator); err != nil {
		t.Fatalf("DecodeDefinitionEnvelope() error = %v", err)
	}
}

func TestDefinitionEnvelopeRejectsInvalidUTF8WithoutLossyPublication(t *testing.T) {
	definition := publishedDefinitionFixture()
	definition.Steps[0].Label = string([]byte{'R', 0xff})
	if _, err := EncodeDefinitionEnvelope([]byte("source"), definition, nil); err == nil ||
		!strings.Contains(err.Error(), "invalid UTF-8") {
		t.Fatalf("EncodeDefinitionEnvelope() error = %v, want invalid UTF-8", err)
	}
}

func TestPublicationEncodingDoesNotChangeExistingIRJSON(t *testing.T) {
	action := Action{Name: "Run", Kind: ActionKindFlow, Flow: &FlowAction{APIName: "Flow1"}}
	encoded, err := json.Marshal(action)
	if err != nil {
		t.Fatalf("json.Marshal(Action) error = %v", err)
	}
	want := `{"Name":"Run","Kind":"flow","Reference":null,"FieldUpdate":null,"Flow":{"APIName":"Flow1"},"Alert":null}`
	if string(encoded) != want {
		t.Fatalf("existing Action JSON changed:\n got %s\nwant %s", encoded, want)
	}

	envelope, err := EncodeDefinitionEnvelope([]byte("source"), publishedDefinitionFixture(), nil)
	if err != nil {
		t.Fatalf("EncodeDefinitionEnvelope() error = %v", err)
	}
	if !bytes.Contains(envelope, []byte(`"definition":{"label":"Review","active":true`)) || bytes.Contains(envelope, []byte(`"Definition"`)) {
		t.Fatalf("versioned envelope did not use its own canonical JSON grammar: %s", envelope)
	}
}
