package automation

import (
	"context"
	"testing"

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

type flowIdentityQueryStub struct {
	ports.QueryService
	calls                     []queryspec.QuerySpec
	securityContexts          []constants.SecurityContext
	missingDefinition         bool
	definitionRequiresScanAll bool
	requireSystemContext      bool
}

func (s *flowIdentityQueryStub) QueryWithSpec(ctx context.Context, spec queryspec.QuerySpec, user *models.UserSession) (*models.QueryResult, error) {
	s.calls = append(s.calls, spec)
	s.securityContexts = append(s.securityContexts, constants.GetSecurityContext(ctx))
	if s.requireSystemContext && constants.GetSecurityContext(ctx) != constants.SystemContext {
		return &models.QueryResult{TotalSize: 0}, nil
	}
	switch spec.ObjectAPIName {
	case constants.TableFlowDefinition:
		if s.missingDefinition || (s.definitionRequiresScanAll && !spec.ScanAll) {
			return &models.QueryResult{TotalSize: 0}, nil
		}
		return &models.QueryResult{
			TotalSize: 1,
			Records: []models.SObject{{
				constants.FieldID: "def-1",
				"DeveloperName":   "Connector_Inbound",
				"ActiveVersionId": "flow-version-1",
				"IsCustomized":    true,
				"OwnerPack":       "connector-data-pack",
				"ProcessType":     "AutoLaunchedFlow",
			}},
		}, nil
	case constants.TableFlow:
		return &models.QueryResult{
			TotalSize: 1,
			Records: []models.SObject{{
				constants.FieldID: "flow-version-1",
				"DefinitionId":    "def-1",
				"Status":          models.FlowStatusActive,
				"Source":          flowSourceStandard,
				"Metadata":        `{}`,
			}},
		}, nil
	default:
		t := "unexpected object query: " + spec.ObjectAPIName
		return nil, &unexpectedFlowIdentityQueryError{message: t}
	}
}

func TestGetActiveFlowVersionStandardActionRootUsesSystemMetadataLookup(t *testing.T) {
	query := &flowIdentityQueryStub{requireSystemContext: true}
	exec := &FlowExecutor{query: query}
	ctx := constants.WithStandardActionExecutionRootFlow(context.Background(), "sa-send-email", "Connector_Inbound")

	version, err := exec.getActiveFlowVersion(ctx, "Connector_Inbound", nil)
	if err != nil {
		t.Fatalf("getActiveFlowVersion: %v", err)
	}
	if version == nil {
		t.Fatal("getActiveFlowVersion returned nil; standard-action root metadata lookup was not elevated")
	}
	if len(query.securityContexts) != 2 {
		t.Fatalf("query security contexts = %v; want two metadata queries", query.securityContexts)
	}
	for i, got := range query.securityContexts {
		if got != constants.SystemContext {
			t.Fatalf("query %d security context = %v; want %v", i, got, constants.SystemContext)
		}
	}
}

func TestGetActiveFlowVersionDoesNotElevateNonRootFlow(t *testing.T) {
	query := &flowIdentityQueryStub{}
	exec := &FlowExecutor{query: query}
	ctx := constants.WithStandardActionExecutionRootFlow(context.Background(), "sa-send-email", "Standard_Action_Root")

	version, err := exec.getActiveFlowVersion(ctx, "Connector_Inbound", nil)
	if err != nil {
		t.Fatalf("getActiveFlowVersion: %v", err)
	}
	if version == nil {
		t.Fatal("getActiveFlowVersion returned nil")
	}
	for i, got := range query.securityContexts {
		if got != constants.UserContext {
			t.Fatalf("query %d security context = %v; want %v", i, got, constants.UserContext)
		}
	}
}

type unexpectedFlowIdentityQueryError struct {
	message string
}

func (e *unexpectedFlowIdentityQueryError) Error() string {
	return e.message
}

func TestGetActiveFlowVersionThreadsDefinitionOwnerPack(t *testing.T) {
	query := &flowIdentityQueryStub{}
	exec := &FlowExecutor{query: query}

	version, err := exec.getActiveFlowVersion(context.Background(), "Connector_Inbound", nil)
	if err != nil {
		t.Fatalf("getActiveFlowVersion: %v", err)
	}
	if version == nil {
		t.Fatal("getActiveFlowVersion returned nil")
	}
	if version.DefinitionOwnerPack != "connector-data-pack" {
		t.Errorf("DefinitionOwnerPack = %q; want connector-data-pack", version.DefinitionOwnerPack)
	}
	if !version.DefinitionIsCustomized {
		t.Error("DefinitionIsCustomized = false; want true from FlowDefinition")
	}
	if version.ProcessType != "AutoLaunchedFlow" {
		t.Errorf("ProcessType = %q; want AutoLaunchedFlow from FlowDefinition fallback", version.ProcessType)
	}
	if version.Source != flowSourceStandard {
		t.Errorf("Source = %q; want %q", version.Source, flowSourceStandard)
	}
	if !version.DefinitionRuntimeLoaded {
		t.Error("DefinitionRuntimeLoaded = false; want true")
	}
	if len(query.calls) != 2 {
		t.Fatalf("query calls = %d; want 2", len(query.calls))
	}
}

func TestGetActiveFlowVersionUsesRuntimeContextCache(t *testing.T) {
	query := &flowIdentityQueryStub{}
	exec := &FlowExecutor{query: query}
	ctx := withFlowRuntimeContext(context.Background(), NewFlowRuntimeContext(context.Background(), nil))

	first, err := exec.getActiveFlowVersion(ctx, "Connector_Inbound", nil)
	if err != nil {
		t.Fatalf("first getActiveFlowVersion: %v", err)
	}
	if first == nil {
		t.Fatal("first getActiveFlowVersion returned nil")
	}
	first.DefinitionOwnerPack = "mutated-by-caller"

	second, err := exec.getActiveFlowVersion(ctx, "Connector_Inbound", nil)
	if err != nil {
		t.Fatalf("second getActiveFlowVersion: %v", err)
	}
	if second == nil {
		t.Fatal("second getActiveFlowVersion returned nil")
	}
	if second.DefinitionOwnerPack != "connector-data-pack" {
		t.Fatalf("cached DefinitionOwnerPack = %q; want defensive cached copy", second.DefinitionOwnerPack)
	}
	if len(query.calls) != 2 {
		t.Fatalf("query calls = %d; want 2 from first load only", len(query.calls))
	}
}

func TestHydrateFlowVersionDefinitionRuntimeFields(t *testing.T) {
	query := &flowIdentityQueryStub{}
	exec := &FlowExecutor{query: query}
	version := &models.FlowVersion{
		Id:           "flow-version-1",
		DefinitionId: "def-1",
		Source:       flowSourceStandard,
	}

	apiName, err := exec.hydrateFlowVersionDefinitionRuntimeFields(context.Background(), version, nil)
	if err != nil {
		t.Fatalf("hydrateFlowVersionDefinitionRuntimeFields: %v", err)
	}
	if apiName != "Connector_Inbound" {
		t.Errorf("apiName = %q; want Connector_Inbound", apiName)
	}
	if version.DefinitionOwnerPack != "connector-data-pack" {
		t.Errorf("DefinitionOwnerPack = %q; want connector-data-pack", version.DefinitionOwnerPack)
	}
	if !version.DefinitionIsCustomized {
		t.Error("DefinitionIsCustomized = false; want true")
	}
	if version.ProcessType != "AutoLaunchedFlow" {
		t.Errorf("ProcessType = %q; want AutoLaunchedFlow", version.ProcessType)
	}
	if !version.DefinitionRuntimeLoaded {
		t.Error("DefinitionRuntimeLoaded = false; want true")
	}
	if len(query.calls) != 1 {
		t.Fatalf("query calls = %d; want 1", len(query.calls))
	}
	if !query.calls[0].ScanAll {
		t.Fatal("FlowDefinition hydration ScanAll = false; want true so resume can read soft-deleted definitions")
	}
}

func TestHydrateFlowVersionDefinitionRuntimeFields_ReadsSoftDeletedDefinition(t *testing.T) {
	query := &flowIdentityQueryStub{definitionRequiresScanAll: true}
	exec := &FlowExecutor{query: query}
	version := &models.FlowVersion{
		Id:           "flow-version-1",
		DefinitionId: "def-1",
		Source:       flowSourceStandard,
	}

	apiName, err := exec.hydrateFlowVersionDefinitionRuntimeFields(context.Background(), version, nil)
	if err != nil {
		t.Fatalf("hydrateFlowVersionDefinitionRuntimeFields: %v", err)
	}
	if apiName != "Connector_Inbound" {
		t.Errorf("apiName = %q; want Connector_Inbound", apiName)
	}
	if version.DefinitionOwnerPack != "connector-data-pack" {
		t.Errorf("DefinitionOwnerPack = %q; want connector-data-pack", version.DefinitionOwnerPack)
	}
	if !version.DefinitionRuntimeLoaded {
		t.Error("DefinitionRuntimeLoaded = false; want true")
	}
}

func TestHydrateFlowVersionDefinitionRuntimeFields_MissingDefinitionFailsClosed(t *testing.T) {
	query := &flowIdentityQueryStub{missingDefinition: true}
	exec := &FlowExecutor{query: query}
	version := &models.FlowVersion{Id: "flow-version-1", DefinitionId: "missing-def"}

	if _, err := exec.hydrateFlowVersionDefinitionRuntimeFields(context.Background(), version, nil); err == nil {
		t.Fatal("hydrateFlowVersionDefinitionRuntimeFields error = nil; want fail-closed missing definition error")
	}
	if version.DefinitionRuntimeLoaded {
		t.Error("DefinitionRuntimeLoaded = true after missing definition; want false")
	}
}

func TestEnsureFlowVersionDefinitionRuntimeFieldsHydratesRawVersion(t *testing.T) {
	query := &flowIdentityQueryStub{}
	exec := &FlowExecutor{query: query}
	version := &models.FlowVersion{Id: "flow-version-1", DefinitionId: "def-1"}

	apiName, err := exec.ensureFlowVersionDefinitionRuntimeFields(context.Background(), version, "Scheduled_Flow", nil)
	if err != nil {
		t.Fatalf("ensureFlowVersionDefinitionRuntimeFields: %v", err)
	}
	if apiName != "Scheduled_Flow" {
		t.Errorf("apiName = %q; want caller-supplied Scheduled_Flow", apiName)
	}
	if version.DefinitionOwnerPack != "connector-data-pack" {
		t.Errorf("DefinitionOwnerPack = %q; want connector-data-pack", version.DefinitionOwnerPack)
	}
	if !version.DefinitionRuntimeLoaded {
		t.Error("DefinitionRuntimeLoaded = false; want true")
	}
}

func TestApplyFlowDefinitionRuntimeFieldsForScheduledVersion(t *testing.T) {
	version := &models.FlowVersion{Id: "flow-version-1"}
	def := &models.FlowDefinition{
		Id:            "def-1",
		DeveloperName: "Scheduled_Flow",
		IsCustomized:  true,
		OwnerPack:     "scheduled-pack",
		ProcessType:   "AutoLaunchedFlow",
	}

	applyFlowDefinitionRuntimeFields(version, def)

	if version.DefinitionOwnerPack != "scheduled-pack" {
		t.Errorf("DefinitionOwnerPack = %q; want scheduled-pack", version.DefinitionOwnerPack)
	}
	if !version.DefinitionIsCustomized {
		t.Error("DefinitionIsCustomized = false; want true")
	}
	if !version.DefinitionRuntimeLoaded {
		t.Error("DefinitionRuntimeLoaded = false; want true")
	}
	if version.ProcessType != "AutoLaunchedFlow" {
		t.Errorf("ProcessType = %q; want AutoLaunchedFlow", version.ProcessType)
	}
}

func TestWithCurrentFlowExecutionUsesFlowVersionProvenance(t *testing.T) {
	version := &models.FlowVersion{
		Id:                     "flow-version-1",
		DefinitionId:           "def-1",
		DefinitionOwnerPack:    "connector-data-pack",
		DefinitionIsCustomized: true,
		Source:                 flowSourceStandard,
	}

	ctx := withCurrentFlowExecution(context.Background(), "Connector_Inbound", version)
	identity, ok := flowRuntimeContextFromCtx(ctx).CurrentFlow()
	if !ok {
		t.Fatal("CurrentFlow() ok = false; want true")
	}
	if identity.OwnerPack != "connector-data-pack" {
		t.Errorf("OwnerPack = %q; want connector-data-pack", identity.OwnerPack)
	}
	if !identity.DefinitionIsCustomized {
		t.Error("DefinitionIsCustomized = false; want true")
	}
	if identity.VersionSource != flowSourceStandard {
		t.Errorf("VersionSource = %q; want %q", identity.VersionSource, flowSourceStandard)
	}
}
