package automation

import (
	"context"
	"errors"
	"fmt"
	"testing"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/api"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/approvalir"
)

type runtimeApprovalDefinitionDecoderFunc func(string) (*approvalir.Definition, error)

func (f runtimeApprovalDefinitionDecoderFunc) DecodeApprovalDefinition(raw string) (*approvalir.Definition, error) {
	return f(raw)
}

type runtimeApproverQueryStub struct {
	usersByUsername map[string]string
}

func (s *runtimeApproverQueryStub) QueryWithFilter(ctx context.Context, objectName, filter string, user *models.UserSession, sortField, sortDir string, limit int) (*models.QueryResult, error) {
	if objectName != constants.TableUser {
		return &models.QueryResult{Records: []models.SObject{}}, nil
	}
	for username, id := range s.usersByUsername {
		if filter == fmt.Sprintf("Id == '%s'", id) || filter == fmt.Sprintf("Username == '%s'", username) {
			return &models.QueryResult{
				TotalSize: 1,
				Records: []models.SObject{{
					constants.FieldID: id,
					"Username":        username,
				}},
			}, nil
		}
	}
	return &models.QueryResult{Records: []models.SObject{}}, nil
}

func (s *runtimeApproverQueryStub) Query(ctx context.Context, req api.QueryRequest, user *models.UserSession) (*models.QueryResult, error) {
	panic("runtimeApproverQueryStub.Query: not implemented")
}

func (s *runtimeApproverQueryStub) QueryWithSpec(context.Context, queryspec.QuerySpec, *models.UserSession) (*models.QueryResult, error) {
	panic("runtimeApproverQueryStub.QueryWithSpec: not implemented")
}

func (s *runtimeApproverQueryStub) RunAnalytics(ctx context.Context, analyticsQuery models.AnalyticsQuery, currentUser *models.UserSession) (interface{}, error) {
	panic("runtimeApproverQueryStub.RunAnalytics: not implemented")
}

func (s *runtimeApproverQueryStub) SearchSingleObject(ctx context.Context, objectName, term string, searchFields []string, returnFields []string, limit int, offset int, orderBy string, orderDir string, filterExpr string, currentUser *models.UserSession) ([]models.SObject, error) {
	panic("runtimeApproverQueryStub.SearchSingleObject: not implemented")
}

func (s *runtimeApproverQueryStub) SearchSuggestions(context.Context, string, string, []string, []string, int, *models.UserSession) ([]models.SObject, error) {
	return nil, nil
}

func (s *runtimeApproverQueryStub) GlobalSearch(ctx context.Context, term string, currentUser *models.UserSession) ([]models.SearchResult, error) {
	panic("runtimeApproverQueryStub.GlobalSearch: not implemented")
}

func (s *runtimeApproverQueryStub) Calculate(ctx context.Context, objectName string, record models.SObject, user *models.UserSession) (models.SObject, error) {
	panic("runtimeApproverQueryStub.Calculate: not implemented")
}

func (s *runtimeApproverQueryStub) ExecuteRawSQL(ctx context.Context, sql string, params []interface{}, user *models.UserSession) ([]models.SObject, error) {
	panic("runtimeApproverQueryStub.ExecuteRawSQL: not implemented")
}

func TestResolveApproverIDsForNodeWithContext_UsesMetadataStepNameFallback(t *testing.T) {
	query := &runtimeApproverQueryStub{
		usersByUsername: map[string]string{
			"first.approver@example.test":  "005-first",
			"second.approver@example.test": "005-second",
		},
	}
	svc := NewApprovalService(nil, query, nil, nil, nil, nil, nil, nil)

	def := models.SObject{
		constants.FieldID: "pd-1",
		"TableEnumOrId":   "Case",
		"Metadata": `{
			"approvalStep": [{
				"name": "Configured_Step",
				"label": "Human Review",
				"assignedApprover": {
					"approver": [
						{ "type": "user", "name": "first.approver@example.test" },
						{ "type": "user", "name": "second.approver@example.test" }
					],
					"whenMultipleApprovers": "Unanimous"
				}
			}]
		}`,
	}
	node := models.SObject{
		constants.FieldID: "pn-1",
		"DeveloperName":   "Runtime_Name_Does_Not_Match_Metadata",
		"Name":            "Human Review",
		"ApproverType":    "User",
		"ApproverUserId":  "legacy.approver@example.test",
	}

	ids, err := svc.resolveApproverIDsForNodeWithContext(
		context.Background(),
		def,
		node,
		"500-record",
		&models.UserSession{ID: "005-submit"},
		approvalApproverResolutionContext{},
	)
	if err != nil {
		t.Fatalf("resolveApproverIDsForNodeWithContext() error = %v", err)
	}

	want := []string{"005-first", "005-second"}
	if len(ids) != len(want) {
		t.Fatalf("ids = %v, want %v", ids, want)
	}
	for i := range want {
		if ids[i] != want[i] {
			t.Fatalf("ids = %v, want %v", ids, want)
		}
	}
}

func TestApprovalModeForNode_UsesMetadataAndIgnoresProcessNodeProjection(t *testing.T) {
	svc := NewApprovalService(nil, nil, nil, nil, nil, nil, nil, nil)
	def := models.SObject{
		constants.FieldID: "pd-1",
		"Metadata": `{
			"approvalStep": [{
				"name": "Configured_Step",
				"label": "Human Review",
				"assignedApprover": {
					"whenMultipleApprovers": "Unanimous"
				}
			}]
		}`,
	}
	node := models.SObject{
		constants.FieldID: "pn-1",
		"DeveloperName":   "Configured_Step",
		"Name":            "Human Review",
		"RoutingType":     "FirstResponse",
	}

	got, err := svc.approvalModeForNode(def, node)
	if err != nil {
		t.Fatalf("approvalModeForNode() error = %v", err)
	}
	if got != "Unanimous" {
		t.Fatalf("approvalModeForNode() = %q, want Unanimous", got)
	}
}

func TestApprovalModeForNode_MetadataStepResolutionFallbacks(t *testing.T) {
	// The definition carries one metadata step ("Metadata_Step") whose
	// whenMultipleApprovers is Unanimous. When the runtime node's names drifted
	// from the metadata, resolution falls back to StepOrder; when the step cannot
	// be resolved at all, the mode defaults to Unanimous. Both paths yield
	// "Unanimous" here.
	tests := []struct {
		name string
		node models.SObject
	}{
		{
			name: "UsesStepOrderWhenNodeNamesDrift",
			node: models.SObject{
				constants.FieldID: "pn-1",
				"DeveloperName":   "Runtime_Name_Does_Not_Match_Metadata",
				"Name":            "Runtime Label",
				"StepOrder":       "1",
			},
		},
		{
			name: "UsesUnanimousWhenMetadataStepCannotBeResolved",
			node: models.SObject{
				constants.FieldID: "pn-1",
				"DeveloperName":   "Different_Runtime_Name",
				"Name":            "Different Runtime Label",
				"StepOrder":       2,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			svc := NewApprovalService(nil, nil, nil, nil, nil, nil, nil, nil)
			def := models.SObject{
				constants.FieldID: "pd-1",
				"Metadata": `{
					"approvalStep": [{
						"name": "Metadata_Step",
						"label": "Metadata Step",
						"assignedApprover": {
							"whenMultipleApprovers": "Unanimous"
						}
					}]
				}`,
			}

			got, err := svc.approvalModeForNode(def, tt.node)
			if err != nil {
				t.Fatalf("approvalModeForNode() error = %v", err)
			}
			if got != "Unanimous" {
				t.Fatalf("approvalModeForNode() = %q, want Unanimous", got)
			}
		})
	}
}

func TestApprovalModeForNode_DefaultsWhenNoMetadataIsPresent(t *testing.T) {
	svc := NewApprovalService(nil, nil, nil, nil, nil, nil, nil, nil)
	def := models.SObject{
		constants.FieldID: "pd-1",
		"Metadata":        "",
	}
	node := models.SObject{
		constants.FieldID: "pn-1",
		"DeveloperName":   "Runtime_Step",
		"Name":            "Runtime Step",
	}

	got, err := svc.approvalModeForNode(def, node)
	if err != nil {
		t.Fatalf("approvalModeForNode() error = %v", err)
	}
	if got != "FirstResponse" {
		t.Fatalf("approvalModeForNode() = %q, want FirstResponse", got)
	}
}

func TestNormalizedCriteriaFromDefinitionPreservesFormula(t *testing.T) {
	criteria := normalizedCriteriaFromDefinition(&approvalir.Criteria{Formula: " Amount > 10 "})
	if criteria == nil || criteria.Formula != "Amount > 10" || criteria.BooleanFilter != "" || len(criteria.CriteriaItems) != 0 {
		t.Fatalf("normalized formula criteria = %#v, want formula-only projection", criteria)
	}
}

func TestCanUserSubmitForProcessFailsClosedOnDecoderError(t *testing.T) {
	wantErr := errors.New("invalid approval definition")
	svc := NewApprovalService(
		nil, nil, nil, nil, nil, nil, nil, nil,
		WithApprovalDefinitionDecoder(runtimeApprovalDefinitionDecoderFunc(func(string) (*approvalir.Definition, error) {
			return nil, wantErr
		})),
	)

	allowed, err := svc.canUserSubmitForProcess(
		context.Background(),
		map[string]interface{}{"Metadata": `{"label":"broken"}`},
		"Account",
		"001-record",
		&models.UserSession{ID: "005-user"},
	)
	if allowed {
		t.Fatal("canUserSubmitForProcess() = true, want decoder error to deny submission")
	}
	if !errors.Is(err, wantErr) || !appErrors.IsInternal(err) {
		t.Fatalf("canUserSubmitForProcess() error = %T %v, want wrapped internal decoder error", err, err)
	}
}

func TestEnsureProcessNodesForDefinitionFailsClosedOnDecoderError(t *testing.T) {
	wantErr := errors.New("invalid approval definition")
	svc := NewApprovalService(
		nil,
		&runtimeApproverQueryStub{},
		nil, nil, nil, nil, nil, nil,
		WithApprovalDefinitionDecoder(runtimeApprovalDefinitionDecoderFunc(func(string) (*approvalir.Definition, error) {
			return nil, wantErr
		})),
	)

	nodes, err := svc.ensureProcessNodesForDefinition(
		context.Background(),
		models.SObject{
			constants.FieldID: "pd-broken",
			"Metadata":        `{"label":"broken"}`,
		},
		&models.UserSession{ID: "005-user"},
	)
	if nodes != nil {
		t.Fatalf("ensureProcessNodesForDefinition() nodes = %v, want nil", nodes)
	}
	if !errors.Is(err, wantErr) || !appErrors.IsInternal(err) {
		t.Fatalf("ensureProcessNodesForDefinition() error = %T %v, want wrapped internal decoder error", err, err)
	}
}
