package automation

import (
	"context"
	"testing"

	"github.com/nexuscrm/kernel/pkg/logging"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

type criteriaSchemaStub struct {
	schemas map[string]*models.ObjectMetadata
}

func (s criteriaSchemaStub) GetSchema(_ context.Context, apiName string) *models.ObjectMetadata {
	return s.schemas[apiName]
}

func TestApprovalServiceMatchesCriterionUsesMultipicklistFieldMetadata(t *testing.T) {
	tagsField := &models.FieldMetadata{
		APIName: "Tags__c",
		Type:    models.FieldType(constants.FieldTypeMultiPicklist),
	}
	textTagsField := &models.FieldMetadata{
		APIName: "TextTags__c",
		Type:    models.FieldType(constants.FieldTypeString),
	}
	svc := NewApprovalService(nil, nil, nil, nil, nil, nil, criteriaSchemaStub{
		schemas: map[string]*models.ObjectMetadata{
			"Widget": {
				APIName: "Widget",
				Fields:  []models.FieldMetadata{*tagsField, *textTagsField},
				FieldsMap: map[string]*models.FieldMetadata{
					"tags__c":     tagsField,
					"texttags__c": textTagsField,
				},
			},
		},
	}, logging.Default)
	record := models.SObject{
		"Tags__c":     "NA;EMEA",
		"TextTags__c": "NA;EMEA",
	}

	tests := []struct {
		name string
		item criterionItem
		want bool
	}{
		{
			name: "plain multipicklist includes token",
			item: criterionItem{field: "Tags__c", operation: "includes", value: "EMEA"},
			want: true,
		},
		{
			name: "same object prefix multipicklist includes token",
			item: criterionItem{field: "Widget.Tags__c", operation: "includes", value: "EMEA"},
			want: true,
		},
		{
			name: "multipicklist includes semicolon literal",
			item: criterionItem{field: "Tags__c", operation: "includes", value: "APAC;EMEA"},
			want: true,
		},
		{
			name: "multipicklist includes comma compatibility literal",
			item: criterionItem{field: "Tags__c", operation: "includes", value: "APAC,EMEA"},
			want: true,
		},
		{
			name: "multipicklist excludes absent token",
			item: criterionItem{field: "Tags__c", operation: "excludes", value: "APAC"},
			want: true,
		},
		{
			name: "multipicklist excludes present token",
			item: criterionItem{field: "Tags__c", operation: "excludes", value: "EMEA"},
			want: false,
		},
		{
			name: "text field keeps scalar includes semantics",
			item: criterionItem{field: "TextTags__c", operation: "includes", value: "EMEA"},
			want: false,
		},
		{
			name: "text field still matches exact scalar includes",
			item: criterionItem{field: "TextTags__c", operation: "includes", value: "NA;EMEA"},
			want: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := svc.matchesCriterion(context.Background(), tt.item, "Widget", record, nil)
			if err != nil {
				t.Fatalf("matchesCriterion() error = %v", err)
			}
			if got != tt.want {
				t.Fatalf("matchesCriterion() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestEvaluateCriterionUsesSharedOperatorSemantics(t *testing.T) {
	tests := []struct {
		name           string
		recordValue    string
		operation      string
		criterionValue string
		want           bool
	}{
		{
			name:           "unknown operator fails closed",
			recordValue:    "100",
			operation:      "unknown",
			criterionValue: "1",
			want:           false,
		},
		{
			name:           "invalid numeric ordering fails closed",
			recordValue:    "not-a-number",
			operation:      "greaterThan",
			criterionValue: "5",
			want:           false,
		},
		{
			name:           "invalid less-or-equal ordering fails closed",
			recordValue:    "not-a-number",
			operation:      "lessOrEqual",
			criterionValue: "5",
			want:           false,
		},
		{
			name:           "numeric ordering matches",
			recordValue:    "10",
			operation:      "greaterOrEqual",
			criterionValue: "10",
			want:           true,
		},
		{
			name:           "date ordering matches",
			recordValue:    "2026-06-22",
			operation:      "greaterThan",
			criterionValue: "2026-06-21",
			want:           true,
		},
		{
			name:           "contains remains case insensitive",
			recordValue:    "North America",
			operation:      "contains",
			criterionValue: "america",
			want:           true,
		},
		{
			name:           "startsWith remains case insensitive",
			recordValue:    "Partner",
			operation:      "startsWith",
			criterionValue: "part",
			want:           true,
		},
		{
			name:           "equals comma literal means any",
			recordValue:    "EMEA",
			operation:      "equals",
			criterionValue: "NA, EMEA",
			want:           true,
		},
		{
			name:           "notEqual comma literal means none",
			recordValue:    "EMEA",
			operation:      "notEqual",
			criterionValue: "NA, EMEA",
			want:           false,
		},
		{
			name:           "scalar includes remains exact equality",
			recordValue:    "NA;EMEA",
			operation:      "includes",
			criterionValue: "EMEA",
			want:           false,
		},
		{
			name:           "scalar includes comma literal means any exact equality",
			recordValue:    "EMEA",
			operation:      "includes",
			criterionValue: "NA, EMEA",
			want:           true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := evaluateCriterion(tt.recordValue, tt.operation, tt.criterionValue); got != tt.want {
				t.Fatalf("evaluateCriterion(%q, %q, %q) = %v, want %v", tt.recordValue, tt.operation, tt.criterionValue, got, tt.want)
			}
		})
	}
}

func TestParseSimpleEntryCriteriaUsesDeclaredApprovalOperators(t *testing.T) {
	items, err := parseSimpleEntryCriteria("Amount greaterOrEqual 10 AND Name startsWith Acme")
	if err != nil {
		t.Fatalf("parseSimpleEntryCriteria() error = %v", err)
	}
	if len(items) != 2 {
		t.Fatalf("len(items) = %d, want 2", len(items))
	}
	if items[0] != (criterionItem{field: "Amount", operation: "greaterOrEqual", value: "10"}) {
		t.Fatalf("items[0] = %+v", items[0])
	}
	if items[1] != (criterionItem{field: "Name", operation: "startsWith", value: "Acme"}) {
		t.Fatalf("items[1] = %+v", items[1])
	}

	if _, err := parseSimpleEntryCriteria("Amount madeUp 10"); err == nil {
		t.Fatal("parseSimpleEntryCriteria() error = nil, want unknown operator error")
	}
}
