package approvalir

import (
	"strings"
	"testing"
)

func TestActionKindIsClosed(t *testing.T) {
	valid := []ActionKind{
		ActionKindFieldUpdate,
		ActionKindFlow,
		ActionKindAlert,
	}
	for _, kind := range valid {
		if !kind.Valid() {
			t.Fatalf("defined kind %q reported invalid", kind)
		}
	}
	if ActionKind("dialect_action").Valid() {
		t.Fatal("undefined kind reported valid")
	}
}

func TestActionValidateAcceptsMatchingInlinePayloadOrReference(t *testing.T) {
	sourceText := "Approved"
	actions := []Action{
		{Name: "stamp status", Kind: ActionKindFieldUpdate, FieldUpdate: &FieldUpdateAction{
			FieldAPIName: "Status",
			Value:        FieldUpdateValue{Kind: FieldUpdateValueKindSourceText, SourceText: &sourceText},
		}},
		{Name: "notify", Kind: ActionKindFlow, Flow: &FlowAction{APIName: "notify_flow"}},
		{Name: "legacy notify", Kind: ActionKindAlert, Alert: &AlertAction{EmailTemplateID: "template"}},
		{Name: "external field update", Kind: ActionKindFieldUpdate, Reference: &ActionReference{APIName: "Set_Status"}},
		{Name: "external flow", Kind: ActionKindFlow, Reference: &ActionReference{APIName: "Notify_Approver"}},
		{Name: "external alert", Kind: ActionKindAlert, Reference: &ActionReference{APIName: "Approval_Alert"}},
	}
	for index := range actions {
		if err := actions[index].Validate(); err != nil {
			t.Fatalf("action %d Validate() error = %v", index, err)
		}
	}
}

func TestActionValidateRejectsInvalidOrMismatchedPayload(t *testing.T) {
	sourceText := "Approved"
	tests := []struct {
		name    string
		action  *Action
		wantErr string
	}{
		{name: "nil action", wantErr: "action is nil"},
		{name: "missing name", action: &Action{Kind: ActionKindFlow, Flow: &FlowAction{APIName: "x"}}, wantErr: "name is required"},
		{name: "unknown kind", action: &Action{Name: "x", Kind: "dialect", Flow: &FlowAction{APIName: "x"}}, wantErr: "invalid kind"},
		{name: "no payload", action: &Action{Name: "x", Kind: ActionKindFlow}, wantErr: "exactly one action payload"},
		{name: "multiple payloads", action: &Action{Name: "x", Kind: ActionKindFlow, Flow: &FlowAction{APIName: "x"}, Alert: &AlertAction{EmailTemplateID: "x"}}, wantErr: "exactly one action payload"},
		{name: "reference mixed with inline payload", action: &Action{Name: "x", Kind: ActionKindFlow, Reference: &ActionReference{APIName: "x"}, Flow: &FlowAction{APIName: "x"}}, wantErr: "exactly one action payload"},
		{name: "reference without API name", action: &Action{Name: "x", Kind: ActionKindFlow, Reference: &ActionReference{}}, wantErr: "reference payload requires an API name"},
		{name: "reference with blank API name", action: &Action{Name: "x", Kind: ActionKindAlert, Reference: &ActionReference{APIName: " \t"}}, wantErr: "reference payload requires an API name"},
		{name: "wrong payload", action: &Action{Name: "x", Kind: ActionKindFlow, Alert: &AlertAction{EmailTemplateID: "x"}}, wantErr: "requires a flow payload"},
		{name: "flow without API name", action: &Action{Name: "x", Kind: ActionKindFlow, Flow: &FlowAction{}}, wantErr: "requires an API name"},
		{name: "alert without template", action: &Action{Name: "x", Kind: ActionKindAlert, Alert: &AlertAction{}}, wantErr: "requires an email-template"},
		{name: "field update without field", action: &Action{Name: "x", Kind: ActionKindFieldUpdate, FieldUpdate: &FieldUpdateAction{Value: FieldUpdateValue{Kind: FieldUpdateValueKindString, String: &sourceText}}}, wantErr: "field API name is required"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			err := test.action.Validate()
			if err == nil || !strings.Contains(err.Error(), test.wantErr) {
				t.Fatalf("Validate() error = %v, want containing %q", err, test.wantErr)
			}
		})
	}
}

func TestFieldUpdateActionValidateRejectsInvalidValue(t *testing.T) {
	action := FieldUpdateAction{
		FieldAPIName: "Status",
		Value:        FieldUpdateValue{Kind: FieldUpdateValueKindString},
	}
	if err := action.Validate(); err == nil || !strings.Contains(err.Error(), "value: kind") {
		t.Fatalf("Validate() error = %v, want invalid value context", err)
	}

	var nilAction *FieldUpdateAction
	if err := nilAction.Validate(); err == nil || !strings.Contains(err.Error(), "field-update action is nil") {
		t.Fatalf("nil Validate() error = %v", err)
	}
}

func TestValidateActionsReportsFailingIndex(t *testing.T) {
	actions := []Action{
		{Name: "notify", Kind: ActionKindFlow, Flow: &FlowAction{APIName: "notify_flow"}},
		{Name: "broken", Kind: ActionKindFlow, Flow: &FlowAction{}},
	}
	if err := validateActions(actions); err == nil || !strings.Contains(err.Error(), "action 1: flow payload requires an API name") {
		t.Fatalf("validateActions() error = %v", err)
	}
}
