package runtimeassembly

import (
	"context"
	"errors"
	"slices"
	"testing"

	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	searchsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/searchv1"
)

type searchScopeOrderSourceFunc func(
	context.Context,
	string,
) ([]ports.MRUObjectInteraction, error)

type searchScopeReadAuthorizerFunc func(
	context.Context,
	string,
	string,
	*models.UserSession,
) bool

func (f searchScopeReadAuthorizerFunc) CheckObjectPermissionWithUser(
	ctx context.Context,
	objectAPIName string,
	operation string,
	viewer *models.UserSession,
) bool {
	return f(ctx, objectAPIName, operation, viewer)
}

func (f searchScopeOrderSourceFunc) GetScopeOrder(
	ctx context.Context,
	viewerID string,
) ([]ports.MRUObjectInteraction, error) {
	return f(ctx, viewerID)
}

func TestSearchScopeOrderProjectionBindsObjectReadAuthorityAndMRUToOneSnapshot(
	t *testing.T,
) {
	floor := permissionProjectionVersion(t, 7)
	viewer := &models.UserSession{ID: "viewer", ProfileID: "profile"}
	transactionBound := func(ctx context.Context) bool {
		bound, _ := ctx.Value(permissionSnapshotContextKey{}).(bool)
		return bound
	}
	projection, err := newSearchScopeOrderProjection(
		searchScopeReadAuthorizerFunc(func(
			ctx context.Context,
			objectAPIName string,
			operation string,
			gotViewer *models.UserSession,
		) bool {
			if !transactionBound(ctx) || gotViewer != viewer ||
				operation != constants.PermRead {
				t.Fatalf(
					"read authority context/viewer/operation = %v/%#v/%q",
					transactionBound(ctx), gotViewer, operation,
				)
			}
			return objectAPIName == "Account" || objectAPIName == "Lead"
		}),
		searchScopeOrderSourceFunc(func(
			ctx context.Context,
			viewerID string,
		) ([]ports.MRUObjectInteraction, error) {
			if !transactionBound(ctx) || viewerID != viewer.ID {
				t.Fatalf(
					"MRU authority context/viewer = %v/%q",
					transactionBound(ctx), viewerID,
				)
			}
			return []ports.MRUObjectInteraction{
				{ObjectAPIName: "lead"},
				{ObjectAPIName: "Hidden"},
				{ObjectAPIName: "Account"},
			}, nil
		}),
		permissionSnapshotTransactionsFunc(func(
			ctx context.Context,
			run func(context.Context) error,
		) error {
			return run(context.WithValue(
				ctx, permissionSnapshotContextKey{}, true,
			))
		}),
		permissionStateSourceFunc(func(
			ctx context.Context,
		) (persistence.PermissionState, error) {
			if !transactionBound(ctx) {
				t.Fatal("Permission State did not receive transaction context")
			}
			return persistence.PermissionState{
				Version: floor, EnforcementMode: persistence.PermissionStateEnforcedMode,
			}, nil
		}),
	)
	if err != nil {
		t.Fatal(err)
	}

	got, err := projection.ScopeOrder(
		t.Context(), viewer,
		[]string{"Account", "Contact", "Lead"}, floor,
	)
	if err != nil {
		t.Fatal(err)
	}
	if !slices.Equal(got, []string{"Lead", "Account"}) {
		t.Fatalf("ScopeOrder() = %v", got)
	}
}

func TestSearchScopeOrderProjectionRejectsChangedStateBeforeAuthorityReads(
	t *testing.T,
) {
	floor := permissionProjectionVersion(t, 7)
	current := permissionProjectionVersion(t, 8)
	permissionCalled := false
	mruCalled := false
	projection, err := newSearchScopeOrderProjection(
		searchScopeReadAuthorizerFunc(func(
			context.Context,
			string,
			string,
			*models.UserSession,
		) bool {
			permissionCalled = true
			return true
		}),
		searchScopeOrderSourceFunc(func(
			context.Context,
			string,
		) ([]ports.MRUObjectInteraction, error) {
			mruCalled = true
			return nil, nil
		}),
		permissionSnapshotTransactionsFunc(func(
			ctx context.Context,
			run func(context.Context) error,
		) error {
			return run(ctx)
		}),
		permissionStateSourceFunc(func(
			context.Context,
		) (persistence.PermissionState, error) {
			return persistence.PermissionState{
				Version: current, EnforcementMode: persistence.PermissionStateEnforcedMode,
			}, nil
		}),
	)
	if err != nil {
		t.Fatal(err)
	}

	_, err = projection.ScopeOrder(
		t.Context(), &models.UserSession{ID: "viewer"},
		[]string{"Account"}, floor,
	)
	var failure *searchsemantic.Failure
	if !errors.As(err, &failure) ||
		failure.Category != commonsemantic.FailureUnavailable ||
		permissionCalled || mruCalled {
		t.Fatalf(
			"changed state = %v, permission=%v, MRU=%v",
			err, permissionCalled, mruCalled,
		)
	}
}

func TestSearchScopeOrderProjectionFailsClosedOnMRUError(t *testing.T) {
	floor := permissionProjectionVersion(t, 7)
	wantErr := errors.New("MRU unavailable")
	projection, err := newSearchScopeOrderProjection(
		searchScopeReadAuthorizerFunc(func(
			context.Context,
			string,
			string,
			*models.UserSession,
		) bool {
			return true
		}),
		searchScopeOrderSourceFunc(func(
			context.Context,
			string,
		) ([]ports.MRUObjectInteraction, error) {
			return nil, wantErr
		}),
		permissionSnapshotTransactionsFunc(func(
			ctx context.Context,
			run func(context.Context) error,
		) error {
			return run(ctx)
		}),
		permissionStateSourceFunc(func(
			context.Context,
		) (persistence.PermissionState, error) {
			return persistence.PermissionState{
				Version: floor, EnforcementMode: persistence.PermissionStateEnforcedMode,
			}, nil
		}),
	)
	if err != nil {
		t.Fatal(err)
	}

	got, err := projection.ScopeOrder(
		t.Context(), &models.UserSession{ID: "viewer"},
		[]string{"Account"}, floor,
	)
	if !errors.Is(err, wantErr) || got != nil {
		t.Fatalf("MRU failure = %v, result=%v", err, got)
	}
}

func TestOrderAuthorizedSearchScopeRejectsDuplicateAuthority(
	t *testing.T,
) {
	_, err := orderAuthorizedSearchScope(
		[]string{"Account", "account"},
		nil,
	)
	var failure *searchsemantic.Failure
	if !errors.As(err, &failure) ||
		failure.Category != commonsemantic.FailureInternal {
		t.Fatalf("duplicate read authority = %v", err)
	}
}
