package automation

import (
	"context"
	"errors"
	"strings"
	"testing"

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

func TestRenderNativeEmailTemplateFieldPreservesVisibilityAuthorityFailure(t *testing.T) {
	authorityErr := errors.New("permission authority unavailable")
	ctx := nativeEmailTemplateContext{
		Record: models.SObject{"Name": "must-not-render"},
		checkRecordFieldVisible: func(string) (bool, error) {
			return false, authorityErr
		},
	}

	rendered, err := renderNativeEmailTemplateField("Subject", "{{Record.Name}}", ctx)
	if rendered != "" {
		t.Fatalf("rendered = %q, want no partial template", rendered)
	}
	if !errors.Is(err, authorityErr) {
		t.Fatalf("error = %T %[1]v, want wrapped authority failure", err)
	}
}

func TestActionServiceLoadAndRenderTemplateNativeRoots(t *testing.T) {
	userEmail := "maker@example.com"
	persistence := &emailTemplatePersistenceStub{
		records: map[string]models.SObject{
			"tmpl-1": {
				constants.FieldSysEmailTemplate_IsActive:  true,
				constants.FieldSysEmailTemplate_Subject:   "Record {{Record.Name}}",
				constants.FieldSysEmailTemplate_HTMLValue: "<a href=\"{{Variables.ApprovalUrl}}\">{{User.Email}}</a>",
				constants.FieldSysEmailTemplate_Body:      "Owner {{User.Name}} sees {{Record.Stage}}",
			},
		},
	}
	service := &ActionService{persistence: persistence}
	actionCtx := &ActionContext{
		Record: models.SObject{
			"Name":  "Quarterly Plan",
			"Stage": "Draft",
		},
		User: &models.UserSession{
			ID:    "005-user",
			Name:  "Template Author",
			Email: &userEmail,
		},
		Variables: map[string]interface{}{
			"ApprovalUrl": "https://nexuscrm.example/approval/1",
		},
	}

	subject, htmlBody, plainBody, err := service.LoadAndRenderTemplate(context.Background(), "tmpl-1", actionCtx, "Opportunity")
	if err != nil {
		t.Fatalf("LoadAndRenderTemplate: %v", err)
	}
	if subject != "Record Quarterly Plan" {
		t.Errorf("subject = %q", subject)
	}
	if htmlBody != "<a href=\"https://nexuscrm.example/approval/1\">maker@example.com</a>" {
		t.Errorf("htmlBody = %q", htmlBody)
	}
	if plainBody != "Owner Template Author sees Draft" {
		t.Errorf("plainBody = %q", plainBody)
	}
	if len(persistence.getRawCalls) != 1 || persistence.getRawCalls[0].objectName != constants.TableEmailTemplate {
		t.Fatalf("GetRaw calls = %#v; want exactly one _System_EmailTemplate lookup", persistence.getRawCalls)
	}
}

func TestActionServiceLoadAndRenderTemplateRejectsNonNativeOrUnresolvedTokens(t *testing.T) {
	userEmail := "maker@example.com"
	tests := []struct {
		name     string
		subject  string
		wantText string
	}{
		{
			name:     "missing record key",
			subject:  "Record {{Record.Name}}",
			wantText: "unresolved merge token {{Record.Name}}",
		},
		{
			name:     "salesforce merge syntax",
			subject:  "Record {!Account.Name}",
			wantText: "Salesforce merge syntax is not supported",
		},
		{
			name:     "unsupported root",
			subject:  "Owner {{Owner.Name}}",
			wantText: "unsupported merge root \"Owner\"",
		},
		{
			name:     "ambiguous bare token",
			subject:  "Approver {{ApproverName}}",
			wantText: "must use exactly one approved root and field name",
		},
		{
			name:     "nested record owner path",
			subject:  "Owner {{Record.Owner.Name}}",
			wantText: "must use exactly one approved root and field name",
		},
		{
			name:     "nested record secret path",
			subject:  "Owner {{Record.Owner.SecretField}}",
			wantText: "must use exactly one approved root and field name",
		},
		{
			name:     "unclosed double brace",
			subject:  "Record {{Record.Name",
			wantText: "malformed double-brace token",
		},
		{
			name:     "stray close double brace",
			subject:  "Record Record.Name}}",
			wantText: "malformed double-brace token",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			persistence := &emailTemplatePersistenceStub{
				records: map[string]models.SObject{
					"tmpl-1": {
						constants.FieldSysEmailTemplate_IsActive: true,
						constants.FieldSysEmailTemplate_Subject:  tt.subject,
					},
				},
			}
			service := &ActionService{persistence: persistence}
			actionCtx := &ActionContext{
				Record: models.SObject{},
				User:   &models.UserSession{Email: &userEmail},
			}

			_, _, _, err := service.LoadAndRenderTemplate(context.Background(), "tmpl-1", actionCtx, "Opportunity")
			if err == nil {
				t.Fatal("LoadAndRenderTemplate succeeded; want validation error")
			}
			if !strings.Contains(err.Error(), tt.wantText) {
				t.Fatalf("error = %q; want substring %q", err.Error(), tt.wantText)
			}
		})
	}
}

func TestActionServiceLoadAndRenderTemplateRejectsHiddenRecordField(t *testing.T) {
	persistence := &emailTemplatePersistenceStub{
		records: map[string]models.SObject{
			"tmpl-1": {
				constants.FieldSysEmailTemplate_IsActive: true,
				constants.FieldSysEmailTemplate_Subject:  "Secret {{Record.Secret__c}}",
			},
		},
	}
	service := &ActionService{
		persistence: persistence,
		permissions: fakeFieldVisibilityChecker{},
	}
	actionCtx := &ActionContext{
		Record: models.SObject{
			"Secret__c": "do-not-send",
		},
		User: &models.UserSession{ID: "005-user", ProfileID: "00e-profile"},
	}

	_, _, _, err := service.LoadAndRenderTemplate(context.Background(), "tmpl-1", actionCtx, "Opportunity")
	if err == nil {
		t.Fatal("LoadAndRenderTemplate succeeded; want hidden field validation error")
	}
	if strings.Contains(err.Error(), "do-not-send") {
		t.Fatalf("error leaked hidden field value: %q", err.Error())
	}
	if !strings.Contains(err.Error(), "unresolved merge token {{Record.Secret__c}}") {
		t.Fatalf("error = %q; want unresolved hidden record token", err.Error())
	}
}

func TestActionServiceLoadAndRenderTemplateAllowsRecordFieldsForBackgroundAction(t *testing.T) {
	persistence := &emailTemplatePersistenceStub{
		records: map[string]models.SObject{
			"tmpl-1": {
				constants.FieldSysEmailTemplate_IsActive: true,
				constants.FieldSysEmailTemplate_Subject:  "Record {{Record.Name}}",
			},
		},
	}
	service := &ActionService{
		persistence: persistence,
		permissions: fakeFieldVisibilityChecker{},
	}
	actionCtx := &ActionContext{
		Record: models.SObject{
			"Name": "Background Plan",
		},
	}

	subject, _, _, err := service.LoadAndRenderTemplate(context.Background(), "tmpl-1", actionCtx, "Opportunity")
	if err != nil {
		t.Fatalf("LoadAndRenderTemplate: %v", err)
	}
	if subject != "Record Background Plan" {
		t.Fatalf("subject = %q", subject)
	}
}

func TestActionServiceLoadAndRenderTemplateRejectsNonScalarMergeValues(t *testing.T) {
	persistence := &emailTemplatePersistenceStub{
		records: map[string]models.SObject{
			"tmpl-1": {
				constants.FieldSysEmailTemplate_IsActive: true,
				constants.FieldSysEmailTemplate_Subject:  "Owner {{Record.Owner}} {{Variables.LookupResult}}",
			},
		},
	}
	service := &ActionService{persistence: persistence}
	actionCtx := &ActionContext{
		Record: models.SObject{
			"Owner": map[string]interface{}{
				"SecretField": "do-not-send",
			},
		},
		Variables: map[string]interface{}{
			"LookupResult": []interface{}{
				map[string]interface{}{"SecretField": "do-not-send"},
			},
		},
	}

	_, _, _, err := service.LoadAndRenderTemplate(context.Background(), "tmpl-1", actionCtx, "Opportunity")
	if err == nil {
		t.Fatal("LoadAndRenderTemplate succeeded; want non-scalar validation error")
	}
	if !strings.Contains(err.Error(), "unresolved merge token {{Record.Owner}}") {
		t.Fatalf("error = %q; want non-scalar record token rejection", err.Error())
	}
	if strings.Contains(err.Error(), "do-not-send") {
		t.Fatalf("error leaked non-scalar value: %q", err.Error())
	}
}

func TestFlowEmailSimplePassesInterviewVariablesToTemplateRenderer(t *testing.T) {
	persistence := &emailTemplatePersistenceStub{
		records: map[string]models.SObject{
			"tmpl-1": {
				constants.FieldSysEmailTemplate_IsActive:  true,
				constants.FieldSysEmailTemplate_Subject:   "Review {{Variables.ApprovalUrl}}",
				constants.FieldSysEmailTemplate_HTMLValue: "<p>{{Record.Name}}</p>",
			},
		},
	}
	sender := &recordingEmailSender{}
	actionService := &ActionService{
		persistence: persistence,
		emailSender: sender,
		formula:     formula.NewEngine(),
		logger:      logging.New(context.Background()),
	}
	executor := newFlowExecutorWithPlatformActionCalls(nil, actionService, nil, nil, nil, nil, logging.New(context.Background()))
	interview := &models.FlowInterview{
		Id: "interview-1",
		ContextData: map[string]interface{}{
			"$Record":     models.SObject{"Name": "Quarterly Plan"},
			"ApprovalUrl": "https://nexuscrm.example/approval/1",
		},
	}
	flowMeta := &models.FlowMetadata{Start: &models.FlowStart{Object: "Opportunity"}}
	action := &models.FlowActionCall{
		Name:       "send_review_email",
		ActionName: "send_review_email",
		ActionType: "emailSimple",
		InputParameters: []models.FlowActionParameter{
			{Name: constants.ConfigEmailTemplate, Value: "tmpl-1"},
			{Name: constants.ConfigRecipient, Value: "person@example.com"},
			{Name: constants.ConfigVariables, Value: map[string]interface{}{
				"ApprovalUrl": map[string]interface{}{"elementReference": "ApprovalUrl"},
			}},
		},
		Connector: &models.FlowConnector{TargetReference: "done"},
	}

	next, err := executor.executeActionCall(context.Background(), interview, flowMeta, action, &models.UserSession{ID: "005-user"})
	if err != nil {
		t.Fatalf("executeActionCall(emailSimple): %v", err)
	}
	if next != "done" {
		t.Fatalf("next = %q; want done", next)
	}
	if len(sender.messages) != 1 {
		t.Fatalf("email sender calls = %d; want 1", len(sender.messages))
	}
	if sender.messages[0].Subject != "Review https://nexuscrm.example/approval/1" {
		t.Fatalf("subject = %q", sender.messages[0].Subject)
	}
	if sender.messages[0].HTML != "<p>Quarterly Plan</p>" {
		t.Fatalf("html = %q", sender.messages[0].HTML)
	}
}

func TestFlowEmailSimpleDoesNotExposeInterviewScratchpadAsTemplateVariables(t *testing.T) {
	persistence := &emailTemplatePersistenceStub{
		records: map[string]models.SObject{
			"tmpl-1": {
				constants.FieldSysEmailTemplate_IsActive: true,
				constants.FieldSysEmailTemplate_Subject:  "Lookup {{Variables.LookupResult}}",
			},
		},
	}
	sender := &recordingEmailSender{}
	actionService := &ActionService{
		persistence: persistence,
		emailSender: sender,
		formula:     formula.NewEngine(),
		logger:      logging.New(context.Background()),
	}
	executor := newFlowExecutorWithPlatformActionCalls(nil, actionService, nil, nil, nil, nil, logging.New(context.Background()))
	interview := &models.FlowInterview{
		Id: "interview-1",
		ContextData: map[string]interface{}{
			"$Record": models.SObject{"Name": "Quarterly Plan"},
			"LookupResult": models.SObject{
				"SecretField": "do-not-send",
			},
		},
	}
	flowMeta := &models.FlowMetadata{Start: &models.FlowStart{Object: "Opportunity"}}
	action := &models.FlowActionCall{
		Name:       "send_review_email",
		ActionName: "send_review_email",
		ActionType: "emailSimple",
		InputParameters: []models.FlowActionParameter{
			{Name: constants.ConfigEmailTemplate, Value: "tmpl-1"},
			{Name: constants.ConfigRecipient, Value: "person@example.com"},
		},
	}

	_, err := executor.executeActionCall(context.Background(), interview, flowMeta, action, &models.UserSession{ID: "005-user"})
	if err == nil {
		t.Fatal("executeActionCall(emailSimple) succeeded; want unresolved template variable")
	}
	if !strings.Contains(err.Error(), "unresolved merge token {{Variables.LookupResult}}") {
		t.Fatalf("error = %q; want unresolved scratchpad variable", err.Error())
	}
	if strings.Contains(err.Error(), "do-not-send") {
		t.Fatalf("error leaked scratchpad value: %q", err.Error())
	}
	if len(sender.messages) != 0 {
		t.Fatalf("email sender calls = %d; want 0 before failed render", len(sender.messages))
	}
}

func TestFlowEmailSimpleDoesNotSerializeExplicitNonScalarTemplateVariable(t *testing.T) {
	persistence := &emailTemplatePersistenceStub{
		records: map[string]models.SObject{
			"tmpl-1": {
				constants.FieldSysEmailTemplate_IsActive: true,
				constants.FieldSysEmailTemplate_Subject:  "Lookup {{Variables.LookupResult}}",
			},
		},
	}
	sender := &recordingEmailSender{}
	actionService := &ActionService{
		persistence: persistence,
		emailSender: sender,
		formula:     formula.NewEngine(),
		logger:      logging.New(context.Background()),
	}
	executor := newFlowExecutorWithPlatformActionCalls(nil, actionService, nil, nil, nil, nil, logging.New(context.Background()))
	interview := &models.FlowInterview{
		Id: "interview-1",
		ContextData: map[string]interface{}{
			"$Record": models.SObject{"Name": "Quarterly Plan"},
			"LookupResult": models.SObject{
				"SecretField": "do-not-send",
			},
		},
	}
	flowMeta := &models.FlowMetadata{Start: &models.FlowStart{Object: "Opportunity"}}
	action := &models.FlowActionCall{
		Name:       "send_review_email",
		ActionName: "send_review_email",
		ActionType: "emailSimple",
		InputParameters: []models.FlowActionParameter{
			{Name: constants.ConfigEmailTemplate, Value: "tmpl-1"},
			{Name: constants.ConfigRecipient, Value: "person@example.com"},
			{Name: constants.ConfigVariables, Value: map[string]interface{}{
				"LookupResult": map[string]interface{}{"elementReference": "LookupResult"},
			}},
		},
	}

	_, err := executor.executeActionCall(context.Background(), interview, flowMeta, action, &models.UserSession{ID: "005-user"})
	if err == nil {
		t.Fatal("executeActionCall(emailSimple) succeeded; want non-scalar template variable")
	}
	if !strings.Contains(err.Error(), "unresolved merge token {{Variables.LookupResult}}") {
		t.Fatalf("error = %q; want non-scalar template variable rejection", err.Error())
	}
	if strings.Contains(err.Error(), "do-not-send") {
		t.Fatalf("error leaked explicit non-scalar value: %q", err.Error())
	}
	if len(sender.messages) != 0 {
		t.Fatalf("email sender calls = %d; want 0 before failed render", len(sender.messages))
	}
}

func TestFlowEmailSimpleRejectsTopLevelRecordTemplateVariablesPayload(t *testing.T) {
	tests := []struct {
		name      string
		variables map[string]interface{}
	}{
		{
			name:      "record",
			variables: map[string]interface{}{"elementReference": "$Record"},
		},
		{
			name:      "lookup result",
			variables: map[string]interface{}{"elementReference": "LookupResult"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			persistence := &emailTemplatePersistenceStub{
				records: map[string]models.SObject{
					"tmpl-1": {
						constants.FieldSysEmailTemplate_IsActive: true,
						constants.FieldSysEmailTemplate_Subject:  "Secret {{Variables.SecretField}}",
					},
				},
			}
			sender := &recordingEmailSender{}
			actionService := &ActionService{
				persistence: persistence,
				emailSender: sender,
				formula:     formula.NewEngine(),
				logger:      logging.New(context.Background()),
			}
			executor := newFlowExecutorWithPlatformActionCalls(nil, actionService, nil, nil, nil, nil, logging.New(context.Background()))
			interview := &models.FlowInterview{
				Id: "interview-1",
				ContextData: map[string]interface{}{
					"$Record": models.SObject{
						"Name":        "Quarterly Plan",
						"SecretField": "do-not-send",
					},
					"LookupResult": models.SObject{
						"SecretField": "do-not-send",
					},
				},
			}
			flowMeta := &models.FlowMetadata{Start: &models.FlowStart{Object: "Opportunity"}}
			action := &models.FlowActionCall{
				Name:       "send_review_email",
				ActionName: "send_review_email",
				ActionType: "emailSimple",
				InputParameters: []models.FlowActionParameter{
					{Name: constants.ConfigEmailTemplate, Value: "tmpl-1"},
					{Name: constants.ConfigRecipient, Value: "person@example.com"},
					{Name: constants.ConfigVariables, Value: tt.variables},
				},
			}

			_, err := executor.executeActionCall(context.Background(), interview, flowMeta, action, &models.UserSession{ID: "005-user"})
			if err == nil {
				t.Fatal("executeActionCall(emailSimple) succeeded; want top-level record variables rejection")
			}
			// Canonical resolveCanonicalSendEmailVariables rejects a top-level
			// elementReference at BUILD time (before template lookup); legacy
			// silently dropped it then failed at render. Both fail closed with
			// no leak; the canonical path fails earlier with a clearer message.
			if !strings.Contains(err.Error(), "variables must be an object literal") {
				t.Fatalf("error = %q; want top-level reference variables rejection", err.Error())
			}
			if strings.Contains(err.Error(), "do-not-send") {
				t.Fatalf("error leaked top-level record variable value: %q", err.Error())
			}
			if len(sender.messages) != 0 {
				t.Fatalf("email sender calls = %d; want 0 before failed render", len(sender.messages))
			}
		})
	}
}

type emailTemplatePersistenceStub struct {
	records     map[string]models.SObject
	getRawCalls []emailTemplateGetRawCall
}

type emailTemplateGetRawCall struct {
	objectName string
	id         string
}

func (s *emailTemplatePersistenceStub) GetRaw(ctx context.Context, objectName string, id string) (models.SObject, error) {
	s.getRawCalls = append(s.getRawCalls, emailTemplateGetRawCall{objectName: objectName, id: id})
	if objectName == constants.TableEmailTemplate {
		return s.records[id], nil
	}
	return nil, nil
}

func (s *emailTemplatePersistenceStub) Insert(context.Context, string, models.SObject, *models.UserSession) (models.SObject, error) {
	return nil, nil
}

func (s *emailTemplatePersistenceStub) PreviewBeforeInsert(context.Context, string, models.SObject, *models.UserSession) (models.SObject, error) {
	return nil, nil
}

func (s *emailTemplatePersistenceStub) PrelockUpdateParents(context.Context, string, string, models.SObject, *models.UserSession) error {
	return nil
}

func (s *emailTemplatePersistenceStub) Update(context.Context, string, string, models.SObject, *models.UserSession) error {
	return nil
}

func (s *emailTemplatePersistenceStub) PreviewBeforeUpdate(context.Context, string, string, models.SObject, models.SObject, *models.UserSession) (*models.PreparedRecordUpdate, error) {
	return nil, nil
}

func (s *emailTemplatePersistenceStub) CommitPreparedUpdate(context.Context, string, string, *models.PreparedRecordUpdate, models.SObject, *models.UserSession) error {
	return nil
}

func (s *emailTemplatePersistenceStub) Delete(context.Context, string, string, *models.UserSession) error {
	return nil
}

func (s *emailTemplatePersistenceStub) GetLock(context.Context, string, string) (models.SObject, error) {
	return nil, nil
}

func (s *emailTemplatePersistenceStub) GetRecordType(context.Context, string) (string, error) {
	return "", nil
}

func (s *emailTemplatePersistenceStub) RunInTransaction(ctx context.Context, fn func(context.Context) error) error {
	return fn(ctx)
}

func (s *emailTemplatePersistenceStub) IsTransactionActive(context.Context) bool {
	return false
}

func (s *emailTemplatePersistenceStub) RunInPessimisticTransaction(ctx context.Context, fn func(context.Context) error) error {
	return fn(ctx)
}

func (s *emailTemplatePersistenceStub) DetachTx(ctx context.Context) context.Context {
	return ctx
}

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

type recordingEmailSender struct {
	messages []*ports.EmailMessage
	result   *ports.EmailSendResult
}

func (s *recordingEmailSender) Send(ctx context.Context, msg *ports.EmailMessage) (*ports.EmailSendResult, error) {
	s.messages = append(s.messages, msg)
	if s.result != nil {
		return s.result, nil
	}
	return ports.AcceptedEmailSendResult(msg, ""), nil
}

type fakeFieldVisibilityChecker struct{}

func (fakeFieldVisibilityChecker) CheckFieldVisibilityWithUser(context.Context, string, string, *models.UserSession) (bool, error) {
	return false, nil
}

type allowingFieldVisibilityChecker struct{}

func (allowingFieldVisibilityChecker) CheckFieldVisibilityWithUser(context.Context, string, string, *models.UserSession) (bool, error) {
	return true, nil
}
