package approvalir

import (
	"errors"
	"strings"
	"testing"
)

func TestDefinitionDecoderFuncReturnsValidatedDefinition(t *testing.T) {
	want := validDefinitionAggregate()
	var gotRaw string
	var decoder DefinitionDecoder = DefinitionDecoderFunc(func(raw string) (*Definition, error) {
		gotRaw = raw
		return want, nil
	})

	got, err := decoder.DecodeApprovalDefinition("product-owned-payload")
	if err != nil {
		t.Fatalf("DecodeApprovalDefinition() error = %v", err)
	}
	if got != want || gotRaw != "product-owned-payload" {
		t.Fatalf("DecodeApprovalDefinition() = (%p, raw %q), want (%p, exact raw)", got, gotRaw, want)
	}
}

func TestDefinitionDecoderFuncAllowsMissingRootLabel(t *testing.T) {
	want := validDefinitionAggregate()
	want.Label = ""
	decoder := DefinitionDecoderFunc(func(string) (*Definition, error) { return want, nil })

	got, err := decoder.DecodeApprovalDefinition("product-owned-payload")
	if err != nil {
		t.Fatalf("DecodeApprovalDefinition() error = %v", err)
	}
	if got != want {
		t.Fatalf("DecodeApprovalDefinition() = %p, want %p", got, want)
	}
}

func TestFormulaValidatingDefinitionDecoderUsesBoundValidator(t *testing.T) {
	const formula = "AND(Amount > 1000, ISPICKVAL(StageName, \"Closed Won\"))"
	want := validDefinitionAggregate()
	want.EntryCriteria = &Criteria{Formula: formula}
	var validated []string
	decoder := NewFormulaValidatingDefinitionDecoder(
		func(string) (*Definition, error) { return want, nil },
		FormulaValidatorFunc(func(source string) error {
			validated = append(validated, source)
			return nil
		}),
	)

	got, err := decoder.DecodeApprovalDefinition("product-owned-payload")
	if err != nil {
		t.Fatalf("DecodeApprovalDefinition() error = %v", err)
	}
	if got != want || len(validated) != 1 || validated[0] != formula {
		t.Fatalf("DecodeApprovalDefinition() = (%p, validated %q), want (%p, exact formula)", got, validated, want)
	}
}

func TestFormulaValidatingDefinitionDecoderFailsClosed(t *testing.T) {
	formulaErr := errors.New("malformed formula")
	decode := DefinitionDecoderFunc(func(string) (*Definition, error) {
		definition := validDefinitionAggregate()
		definition.EntryCriteria = &Criteria{Formula: "Amount >"}
		return definition, nil
	})
	tests := []struct {
		name      string
		validator FormulaValidator
		wantErr   string
		is        error
	}{
		{name: "missing validator", wantErr: "formula validator is nil"},
		{name: "typed nil validator", validator: FormulaValidatorFunc(nil), wantErr: "formula validator is nil"},
		{
			name:      "invalid formula",
			validator: FormulaValidatorFunc(func(string) error { return formulaErr }),
			wantErr:   "formula criteria are invalid",
			is:        formulaErr,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			got, err := NewFormulaValidatingDefinitionDecoder(decode, test.validator).DecodeApprovalDefinition("payload")
			if got != nil || err == nil || !strings.Contains(err.Error(), test.wantErr) {
				t.Fatalf("DecodeApprovalDefinition() = (%#v, %v), want nil and %q", got, err, test.wantErr)
			}
			if test.is != nil && !errors.Is(err, test.is) {
				t.Fatalf("error %v does not wrap %v", err, test.is)
			}
		})
	}
}

func TestDefinitionDecoderFuncFailsClosed(t *testing.T) {
	decodeErr := errors.New("malformed product payload")
	tests := []struct {
		name    string
		decoder DefinitionDecoderFunc
		raw     string
		wantErr string
		is      error
	}{
		{name: "nil decoder", wantErr: "decoder is nil"},
		{name: "decode error", decoder: func(string) (*Definition, error) { return nil, decodeErr }, wantErr: "decode approval definition", is: decodeErr},
		{name: "nil definition", decoder: func(string) (*Definition, error) { return nil, nil }, raw: "payload", wantErr: "returned nil"},
		{name: "nil definition for empty payload", decoder: func(string) (*Definition, error) { return nil, nil }, wantErr: "returned nil"},
		{name: "invalid definition", decoder: func(string) (*Definition, error) {
			definition := validDefinitionAggregate()
			definition.Policy.RecordEditability = ""
			return definition, nil
		}, wantErr: "decoded approval definition is invalid"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			got, err := test.decoder.DecodeApprovalDefinition(test.raw)
			if got != nil || err == nil || !strings.Contains(err.Error(), test.wantErr) {
				t.Fatalf("DecodeApprovalDefinition() = (%#v, %v), want nil and %q", got, err, test.wantErr)
			}
			if test.is != nil && !errors.Is(err, test.is) {
				t.Fatalf("error %v does not wrap %v", err, test.is)
			}
		})
	}
}
