package pipeline

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

func TestResolvePipelineAppliesValidationPolicyFieldByField(t *testing.T) {
	profile := baselineProfile()
	profile.ValidationPolicy = validationPolicyOverride(
		ProducerStructuralFailureRejectOutput,
		SemanticRejectionRejectOutput,
		ValidatorFailureFailRun,
	)
	profile.Chunk.ValidationPolicy = validationPolicyOverride("", "", ValidatorFailureWarnContinue)
	lane := profile.Artifacts["events"]
	lane.Extract.ValidationPolicy = validationPolicyOverride("", SemanticRejectionFailRun, "")
	lane.Merge.ValidationPolicy = validationPolicyOverride("", "", ValidatorFailureWarnContinue)
	profile.Artifacts["events"] = lane

	resolved, err := ResolvePipeline(profile, ResolveOptions{}, newProfileCatalog(t))
	if err != nil {
		t.Fatalf("ResolvePipeline() error = %v", err)
	}
	if got, want := resolved.ChunkValidationPolicy, (ValidationPolicy{ProducerStructuralFailure: ProducerStructuralFailureRejectOutput, SemanticRejection: SemanticRejectionRejectOutput, ValidatorFailure: ValidatorFailureWarnContinue}); got != want {
		t.Fatalf("chunk validation policy = %#v, want %#v", got, want)
	}
	resolvedLane := resolved.Steps[0].ArtifactLanes[0]
	if got, want := resolvedLane.ExtractValidationPolicy, (ValidationPolicy{ProducerStructuralFailure: ProducerStructuralFailureRejectOutput, SemanticRejection: SemanticRejectionFailRun, ValidatorFailure: ValidatorFailureFailRun}); got != want {
		t.Fatalf("extract validation policy = %#v, want %#v", got, want)
	}
	if got, want := resolvedLane.MergeValidationPolicy, (ValidationPolicy{ProducerStructuralFailure: ProducerStructuralFailureRejectOutput, SemanticRejection: SemanticRejectionRejectOutput, ValidatorFailure: ValidatorFailureWarnContinue}); got != want {
		t.Fatalf("merge validation policy = %#v, want %#v", got, want)
	}
	if got, want := resolvedLane.NormalizeValidationPolicy, (ValidationPolicy{ProducerStructuralFailure: ProducerStructuralFailureRejectOutput, SemanticRejection: SemanticRejectionRejectOutput, ValidatorFailure: ValidatorFailureFailRun}); got != want {
		t.Fatalf("normalize validation policy = %#v, want %#v", got, want)
	}

	*profile.ValidationPolicy.SemanticRejection = SemanticRejectionFailRun
	if got := resolved.ConfiguredValidationPolicy.SemanticRejection; got == nil || *got != SemanticRejectionRejectOutput {
		t.Fatalf("resolved configured policy aliases profile: %#v", resolved.ConfiguredValidationPolicy)
	}
}

func TestResolvePipelineRejectsStructuralPolicyOverrideOnDeterministicProducer(t *testing.T) {
	profile := baselineProfile()
	profile.Chunk.ValidationPolicy = validationPolicyOverride(ProducerStructuralFailureRejectOutput, "", "")
	_, err := ResolvePipeline(profile, ResolveOptions{}, newProfileCatalog(t))
	if err == nil || !strings.Contains(err.Error(), "producer_structural_failure") || !strings.Contains(err.Error(), "deterministic") {
		t.Fatalf("ResolvePipeline() error = %v, want deterministic structural-policy rejection", err)
	}
}

func TestValidationPolicyChangesResolvedDigestAndRoundTripsBindings(t *testing.T) {
	left := baselineProfile()
	right := baselineProfile()
	right.ValidationPolicy = validationPolicyOverride("", SemanticRejectionRejectOutput, "")

	leftResolved, err := ResolvePipeline(left, ResolveOptions{}, newProfileCatalog(t))
	if err != nil {
		t.Fatalf("ResolvePipeline(left) error = %v", err)
	}
	rightResolved, err := ResolvePipeline(right, ResolveOptions{}, newProfileCatalog(t))
	if err != nil {
		t.Fatalf("ResolvePipeline(right) error = %v", err)
	}
	if leftResolved.Digest == rightResolved.Digest {
		t.Fatal("validation policy change did not change resolved digest")
	}

	binding := ModuleBinding{Module: "producer", ValidationPolicy: validationPolicyOverride(ProducerStructuralFailureRejectOutput, SemanticRejectionRejectOutput, ValidatorFailureFailRun)}
	encoded, err := json.Marshal(binding)
	if err != nil {
		t.Fatalf("marshal binding: %v", err)
	}
	var decoded ModuleBinding
	if err := json.Unmarshal(encoded, &decoded); err != nil {
		t.Fatalf("unmarshal binding: %v", err)
	}
	if decoded.ValidationPolicy == nil || *decoded.ValidationPolicy.ProducerStructuralFailure != ProducerStructuralFailureRejectOutput || *decoded.ValidationPolicy.SemanticRejection != SemanticRejectionRejectOutput || *decoded.ValidationPolicy.ValidatorFailure != ValidatorFailureFailRun {
		t.Fatalf("round-tripped validation policy = %#v", decoded.ValidationPolicy)
	}
	cloned := cloneModuleBinding(binding)
	*binding.ValidationPolicy.ValidatorFailure = ValidatorFailureWarnContinue
	if got := *cloned.ValidationPolicy.ValidatorFailure; got != ValidatorFailureFailRun {
		t.Fatalf("cloned validation policy aliases binding: %q", got)
	}
}

func validationPolicyOverride(structural ProducerStructuralFailureAction, semantic SemanticRejectionAction, validator ValidatorFailureAction) *ValidationPolicyOverride {
	policy := &ValidationPolicyOverride{}
	if structural != "" {
		policy.ProducerStructuralFailure = &structural
	}
	if semantic != "" {
		policy.SemanticRejection = &semantic
	}
	if validator != "" {
		policy.ValidatorFailure = &validator
	}
	return policy
}
