package approvalir

import (
	"bytes"
	"strings"
	"testing"
)

func TestPublicationBundleRoundTripAndDigest(t *testing.T) {
	envelopeRaw, err := EncodeDefinitionEnvelope([]byte("source"), &Definition{
		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{},
	}, nil)
	if err != nil {
		t.Fatal(err)
	}
	envelope, err := DecodeDefinitionEnvelope(envelopeRaw, nil)
	if err != nil {
		t.Fatal(err)
	}
	bundle := PublicationBundle{
		Schema:       PublicationBundleSchemaV1,
		SourceDigest: "sha256:" + strings.Repeat("a", 64),
		Definitions: []PublishedDefinition{{
			DefinitionID: "approval.review", ObjectID: "WorkItem",
			DefinitionName: "Review", Envelope: *envelope,
		}},
	}
	raw, digest, err := EncodePublicationBundle(bundle, nil)
	if err != nil {
		t.Fatal(err)
	}
	decoded, decodedDigest, err := DecodePublicationBundle(raw, nil)
	if err != nil {
		t.Fatal(err)
	}
	if decodedDigest != digest || len(decoded.Definitions) != 1 || decoded.Definitions[0].DefinitionID != "approval.review" {
		t.Fatalf("decoded bundle = %#v, digest = %q, want %q", decoded, decodedDigest, digest)
	}
}

func TestPublicationBundleRejectsNonCanonicalAndDuplicateAuthority(t *testing.T) {
	validRaw, _, err := EncodePublicationBundle(testPublicationBundle(t), nil)
	if err != nil {
		t.Fatal(err)
	}
	formatted := append([]byte("\n"), validRaw...)
	if _, _, err := DecodePublicationBundle(formatted, nil); err == nil || !strings.Contains(err.Error(), "not canonical") {
		t.Fatalf("DecodePublicationBundle(formatted) error = %v", err)
	}
	bundle := testPublicationBundle(t)
	bundle.Definitions = append(bundle.Definitions, bundle.Definitions[0])
	if _, _, err := EncodePublicationBundle(bundle, nil); err == nil || !strings.Contains(err.Error(), "duplicated") {
		t.Fatalf("EncodePublicationBundle(duplicate) error = %v", err)
	}
	tampered := bytes.Replace(validRaw, []byte(PublicationBundleSchemaV1), []byte("nexuscrm.approval-publication-bundle/v2"), 1)
	if _, _, err := DecodePublicationBundle(tampered, nil); err == nil || !strings.Contains(err.Error(), "unsupported") {
		t.Fatalf("DecodePublicationBundle(schema) error = %v", err)
	}
	bundle = testPublicationBundle(t)
	bundle.Definitions[0].ObjectID = "product.object"
	if _, _, err := EncodePublicationBundle(bundle, nil); err == nil || !strings.Contains(err.Error(), "objectId") {
		t.Fatalf("EncodePublicationBundle(objectId) error = %v", err)
	}
}

func TestPublicationBundleRejectsInvalidUTF8BeforeEncoding(t *testing.T) {
	bundle := testPublicationBundle(t)
	bundle.Definitions[0].Envelope.Definition.Steps[0].Label = string([]byte{'R', 0xff})
	if _, _, err := EncodePublicationBundle(bundle, nil); err == nil ||
		!strings.Contains(err.Error(), "invalid UTF-8") {
		t.Fatalf("EncodePublicationBundle() error = %v, want invalid UTF-8", err)
	}
}

func testPublicationBundle(t *testing.T) PublicationBundle {
	t.Helper()
	envelopeRaw, err := EncodeDefinitionEnvelope([]byte("source"), &Definition{
		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{},
	}, nil)
	if err != nil {
		t.Fatal(err)
	}
	envelope, err := DecodeDefinitionEnvelope(envelopeRaw, nil)
	if err != nil {
		t.Fatal(err)
	}
	return PublicationBundle{Schema: PublicationBundleSchemaV1,
		SourceDigest: "sha256:" + strings.Repeat("a", 64), Definitions: []PublishedDefinition{{
			DefinitionID: "approval.review", ObjectID: "WorkItem", DefinitionName: "Review", Envelope: *envelope,
		}}}
}
