package flowactions

import (
	"context"
	"os"
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
)

func TestRegisterNormalizesAndDefaultsRegistration(t *testing.T) {
	restore := ResetForTest()
	t.Cleanup(restore)
	installTestReservations(t)

	err := Register(Registration{
		ActionType:   " ResolveIssue ",
		HandlerRef:   " host:workflow/resolve-issue ",
		AtomicPolicy: AtomicPolicySafe,
		Handler: func(context.Context, Invocation) (string, error) {
			return "", nil
		},
	})
	if err != nil {
		t.Fatalf("Register() error = %v", err)
	}

	descriptors := Descriptors()
	if len(descriptors) != 1 {
		t.Fatalf("len(Descriptors()) = %d, want 1", len(descriptors))
	}
	got := descriptors[0]
	if got.ActionType != "resolveissue" {
		t.Fatalf("ActionType = %q, want resolveissue", got.ActionType)
	}
	if got.HandlerRef != "host:workflow/resolve-issue" {
		t.Fatalf("HandlerRef = %q, want trimmed handler ref", got.HandlerRef)
	}
	if got.AtomicPolicy != "safe" {
		t.Fatalf("AtomicPolicy = %q, want safe", got.AtomicPolicy)
	}
	if got.PreviewPolicy != "reject" {
		t.Fatalf("PreviewPolicy = %q, want reject default", got.PreviewPolicy)
	}
	if got.RequiresContext != "none" {
		t.Fatalf("RequiresContext = %q, want none default", got.RequiresContext)
	}
}

func TestRegisterRejectsDuplicateNormalizedActionType(t *testing.T) {
	restore := ResetForTest()
	t.Cleanup(restore)
	installTestReservations(t)

	registration := Registration{
		ActionType:   "ResolveIssue",
		HandlerRef:   "host:workflow/resolve-issue",
		AtomicPolicy: AtomicPolicySafe,
		Handler: func(context.Context, Invocation) (string, error) {
			return "", nil
		},
	}
	if err := Register(registration); err != nil {
		t.Fatalf("Register() first error = %v", err)
	}
	registration.ActionType = " resolveissue "
	err := Register(registration)
	if err == nil {
		t.Fatal("Register() duplicate error = nil")
	}
	if !strings.Contains(err.Error(), "duplicate flow action-call registration") {
		t.Fatalf("Register() error = %v, want duplicate diagnostic", err)
	}
	if !strings.Contains(err.Error(), `normalized actionType "resolveissue"`) {
		t.Fatalf("Register() error = %v, want normalized actionType collision", err)
	}
}

func TestRegisterRejectsDynamicallyReservedActionType(t *testing.T) {
	restore := ResetForTest()
	t.Cleanup(restore)

	if err := ReserveActionTypes(" RuntimeOwnedAction "); err != nil {
		t.Fatalf("ReserveActionTypes() error = %v", err)
	}
	err := Register(Registration{
		ActionType:   "runtimeownedaction",
		HandlerRef:   "host:workflow/runtime-owned",
		AtomicPolicy: AtomicPolicySafe,
		Handler: func(context.Context, Invocation) (string, error) {
			return "", nil
		},
	})
	if err == nil {
		t.Fatal("Register() reserved action error = nil")
	}
	if !strings.Contains(err.Error(), `reserved actionType "runtimeownedaction"`) {
		t.Fatalf("Register() error = %v, want reserved actionType collision", err)
	}
	if got := Registrations(); len(got) != 0 {
		t.Fatalf("len(Registrations()) = %d, want 0 after reserved collision", len(got))
	}
}

func TestRegisterBeforeReservationInstallationAcceptsValidExternalAction(t *testing.T) {
	restore := ResetForTest()
	t.Cleanup(restore)

	err := Register(Registration{
		ActionType:   "EarlyExternalAction",
		HandlerRef:   "host:workflow/early-external",
		AtomicPolicy: AtomicPolicySafe,
		Handler: func(context.Context, Invocation) (string, error) {
			return "", nil
		},
	})
	if err != nil {
		t.Fatalf("Register() before reservation installation error = %v, want nil for valid external action", err)
	}
	if got := Registrations(); len(got) != 1 || got[0].ActionType != "earlyexternalaction" {
		t.Fatalf("Registrations() = %#v, want normalized early external registration", got)
	}
}

func TestRegisterRejectsPlatformReservedActionTypeWithoutReservation(t *testing.T) {
	for _, tc := range []struct {
		name       string
		actionType string
	}{
		{name: "flow", actionType: "flow"},
		{name: "sendEmail", actionType: "sendEmail"},
		{name: "PushToConnector", actionType: ActionTypePushToConnector},
	} {
		t.Run(tc.name, func(t *testing.T) {
			restore := ResetForTest()
			t.Cleanup(restore)

			err := Register(Registration{
				ActionType:   tc.actionType,
				HandlerRef:   "host:workflow/runtime-owned",
				AtomicPolicy: AtomicPolicySafe,
				Handler: func(context.Context, Invocation) (string, error) {
					return "", nil
				},
			})
			if err == nil {
				t.Fatalf("Register(%q) error = nil", tc.actionType)
			}
			normalized := strings.ToLower(strings.TrimSpace(tc.actionType))
			if !strings.Contains(err.Error(), `platform-reserved actionType "`+normalized+`"`) {
				t.Fatalf("Register(%q) error = %v, want platform-reserved actionType collision", tc.actionType, err)
			}
			if got := Registrations(); len(got) != 0 {
				t.Fatalf("len(Registrations()) = %d, want 0 after platform-reserved collision", len(got))
			}
		})
	}
}

func TestPlatformReservedActionTypesExcludeTemporaryEmailAliases(t *testing.T) {
	reserved := normalizedActionTypeSet(PlatformReservedActionTypes())
	for _, actionType := range []string{"emailSimple", "emailAlert"} {
		if _, ok := reserved[normalizeActionType(actionType)]; ok {
			t.Fatalf("PlatformReservedActionTypes() contains temporary email alias %q", actionType)
		}
	}
}

func TestIsPlatformReservedActionTypeNormalizesHostOnlyType(t *testing.T) {
	if !IsPlatformReservedActionType(" EnqueueOutboundMail ") {
		t.Fatal("IsPlatformReservedActionType() = false for reserved host-only action")
	}
	if IsPlatformReservedActionType("appOwnedAction") {
		t.Fatal("IsPlatformReservedActionType() = true for app-owned action")
	}
}

func TestReserveActionTypesRejectsAlreadyRegisteredActionType(t *testing.T) {
	restore := ResetForTest()
	t.Cleanup(restore)

	if err := Register(Registration{
		ActionType:   "RuntimeOwnedAction",
		HandlerRef:   "host:workflow/runtime-owned",
		AtomicPolicy: AtomicPolicySafe,
		Handler: func(context.Context, Invocation) (string, error) {
			return "", nil
		},
	}); err != nil {
		t.Fatalf("Register() before reservation installation error = %v, want nil", err)
	}
	err := ReserveActionTypes(" runtimeownedaction ")
	if err == nil {
		t.Fatal("ReserveActionTypes() collision error = nil")
	}
	if !strings.Contains(err.Error(), `conflicts with registered actionType "runtimeownedaction"`) {
		t.Fatalf("ReserveActionTypes() error = %v, want registered actionType collision", err)
	}
}

func TestResetForTestGuardRecognizesOnlyGoTestExecutables(t *testing.T) {
	for _, tc := range []struct {
		name       string
		executable string
		want       bool
	}{
		{name: "go test binary", executable: "/private/tmp/flowactions.test", want: true},
		{name: "production server", executable: "/usr/local/bin/nexuscrm-server", want: false},
		{name: "suffix only not enough", executable: "/usr/local/bin/nexuscrm-test", want: false},
		{name: "empty", executable: "", want: false},
	} {
		t.Run(tc.name, func(t *testing.T) {
			if got := isGoTestExecutable(tc.executable); got != tc.want {
				t.Fatalf("isGoTestExecutable(%q) = %v, want %v", tc.executable, got, tc.want)
			}
		})
	}
}

func TestResetForTestIsAvailableUnderGoTest(t *testing.T) {
	restore := ResetForTest()
	restore()
}

func TestResetForTestPanicsOutsideGoTestExecutable(t *testing.T) {
	originalArgs := os.Args
	os.Args = []string{"/usr/local/bin/nexuscrm-server"}
	t.Cleanup(func() {
		os.Args = originalArgs
	})

	defer func() {
		got := recover()
		if got == nil {
			t.Fatal("ResetForTest() panic = nil, want production guard panic")
		}
		if !strings.Contains(got.(string), "only available during go test") {
			t.Fatalf("ResetForTest() panic = %q, want go test guard message", got)
		}
	}()
	ResetForTest()
}

func TestRegisterBatchIsAtomicOnLaterError(t *testing.T) {
	restore := ResetForTest()
	t.Cleanup(restore)
	installTestReservations(t)

	valid := Registration{
		ActionType:   "ResolveIssue",
		HandlerRef:   "host:workflow/resolve-issue",
		AtomicPolicy: AtomicPolicySafe,
		Handler: func(context.Context, Invocation) (string, error) {
			return "", nil
		},
	}
	invalid := Registration{
		ActionType:   "BadAction",
		AtomicPolicy: AtomicPolicySafe,
		Handler: func(context.Context, Invocation) (string, error) {
			return "", nil
		},
	}
	err := Register(valid, invalid)
	if err == nil {
		t.Fatal("Register() error = nil, want invalid later registration error")
	}
	if got := Registrations(); len(got) != 0 {
		t.Fatalf("len(Registrations()) = %d, want 0 after failed batch", len(got))
	}
}

func TestRegisterRejectsAfterSeal(t *testing.T) {
	restore := ResetForTest()
	t.Cleanup(restore)
	installTestReservations(t)

	Seal()
	err := Register(Registration{
		ActionType:   "ResolveIssue",
		HandlerRef:   "host:workflow/resolve-issue",
		AtomicPolicy: AtomicPolicySafe,
		Handler: func(context.Context, Invocation) (string, error) {
			return "", nil
		},
	})
	if err == nil {
		t.Fatal("Register() after seal error = nil")
	}
	if !strings.Contains(err.Error(), "flow action-call registrations are sealed") {
		t.Fatalf("Register() error = %v, want sealed registry diagnostic", err)
	}
}

func TestReserveActionTypesAllowsIdempotentReservationsAfterSeal(t *testing.T) {
	restore := ResetForTest()
	t.Cleanup(restore)

	if err := ReserveActionTypes(" RuntimeOwnedAction "); err != nil {
		t.Fatalf("ReserveActionTypes() first error = %v", err)
	}
	Seal()
	if err := ReserveActionTypes("runtimeownedaction"); err != nil {
		t.Fatalf("ReserveActionTypes() repeated sealed reservation error = %v, want idempotent nil", err)
	}

	err := ReserveActionTypes("OtherRuntimeAction")
	if err == nil {
		t.Fatal("ReserveActionTypes() new sealed reservation error = nil")
	}
	if !strings.Contains(err.Error(), "flow action-call registrations are sealed") {
		t.Fatalf("ReserveActionTypes() error = %v, want sealed registry diagnostic", err)
	}
}

func TestPlatformReservedActionTypes_IncludesSubmitForApproval(t *testing.T) {
	if ActionTypeSubmitForApproval != constants.ActionTypeSubmitForApproval {
		t.Fatalf("ActionTypeSubmitForApproval = %q, want the shared constant %q", ActionTypeSubmitForApproval, constants.ActionTypeSubmitForApproval)
	}
	for _, actionType := range PlatformReservedActionTypes() {
		if actionType == ActionTypeSubmitForApproval {
			return
		}
	}
	t.Fatalf("PlatformReservedActionTypes() = %v, want it to contain %q", PlatformReservedActionTypes(), ActionTypeSubmitForApproval)
}

func TestRegister_SubmitForApprovalConflictsWithPlatformReservation(t *testing.T) {
	restore := ResetForTest()
	t.Cleanup(restore)

	err := Register(Registration{
		ActionType:   "submitForApproval",
		HandlerRef:   "host:workflow/submit-for-approval",
		AtomicPolicy: AtomicPolicySafe,
		Handler: func(context.Context, Invocation) (string, error) {
			return "", nil
		},
	})
	if err == nil {
		t.Fatal("Register(submitForApproval) error = nil, want platform-reserved actionType collision")
	}
	if !strings.Contains(err.Error(), `platform-reserved actionType "submitforapproval"`) {
		t.Fatalf("Register(submitForApproval) error = %v, want platform-reserved actionType collision", err)
	}
	if got := Registrations(); len(got) != 0 {
		t.Fatalf("len(Registrations()) = %d, want 0 after platform-reserved collision", len(got))
	}
}

func TestPlatformReservedActionTypes_IncludesSendNotification(t *testing.T) {
	for _, actionType := range PlatformReservedActionTypes() {
		if actionType == ActionTypeSendNotification {
			return
		}
	}
	t.Fatalf("PlatformReservedActionTypes() = %v, want it to contain %q", PlatformReservedActionTypes(), ActionTypeSendNotification)
}

func TestRegister_SendNotificationConflictsWithPlatformReservation(t *testing.T) {
	for _, actionType := range []string{"sendNotification", "SendNotification", "sendnotification"} {
		t.Run(actionType, func(t *testing.T) {
			restore := ResetForTest()
			t.Cleanup(restore)

			err := Register(Registration{
				ActionType:   actionType,
				HandlerRef:   "host:workflow/send-notification",
				AtomicPolicy: AtomicPolicySafe,
				Handler: func(context.Context, Invocation) (string, error) {
					return "", nil
				},
			})
			if err == nil {
				t.Fatalf("Register(%q) error = nil, want platform-reserved actionType collision", actionType)
			}
			if !strings.Contains(err.Error(), `platform-reserved actionType "sendnotification"`) {
				t.Fatalf("Register(%q) error = %v, want platform-reserved actionType collision", actionType, err)
			}
			if got := Registrations(); len(got) != 0 {
				t.Fatalf("len(Registrations()) = %d, want 0 after platform-reserved collision", len(got))
			}
		})
	}
}

func installTestReservations(t *testing.T) {
	t.Helper()
	if err := ReserveActionTypes(); err != nil {
		t.Fatalf("ReserveActionTypes() error = %v", err)
	}
}
