package permissions

import (
	"context"
	"errors"
	"reflect"
	"testing"

	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
)

func TestTransactionCurrentPermissionAuthoringPolicyIgnoresRequestSnapshotAndUsesCurrentProfile(t *testing.T) {
	order := []string{}
	roleID := "role-current"
	roleName := "Session Role"
	permissionRepo := &permissionAuthoringCurrentPermissionRepo{
		order:   &order,
		profile: &models.Profile{ID: "profile-current", IsActive: true},
		version: permissionAuthoringPermissionStateVersionForTest(t, 7),
		effective: map[string]bool{
			string(constants.PermManageProfilesPermSets): false,
		},
	}
	userRepo := &permissionAuthoringCurrentUserRepo{
		order: &order,
		user: &models.User{
			ID: "actor-1", Username: "actor@example.test", FirstName: "Current", LastName: "Actor",
			ProfileID: "profile-current", UserRoleID: &roleID, IsActive: true,
		},
	}
	policy, err := NewTransactionCurrentPermissionAuthoringPolicy(
		&PermissionService{repo: permissionRepo, userRepo: userRepo},
		&permissionAuthoringCurrentStateFence{order: &order},
	)
	if err != nil {
		t.Fatal(err)
	}

	// The request was admitted with the old profile and a positive cached grant.
	// A fresh command must still use the locked current profile and current DB
	// grants after the Permission State fence.
	ctx := WithEffectivePermissions(
		context.Background(),
		"actor-1",
		"profile-stale",
		map[string]bool{string(constants.PermManageProfilesPermSets): true},
		nil,
	)
	sessionActor := &models.UserSession{
		ID: "actor-1", Name: "Session Actor", ProfileID: "profile-current", ProfileName: "Session Profile",
		RoleID: &roleID, RoleName: &roleName,
	}
	authorization, authorized, err := policy.AuthorizePermissionSetAuthoring(
		ctx,
		permissionAuthoringPermissionStateVersionForTest(t, 7),
		sessionActor,
	)
	if err != nil {
		t.Fatal(err)
	}
	if authorized {
		t.Fatal("fresh command reused the request-scoped positive permission snapshot")
	}
	if authorization == nil || authorization.FormulaActor == nil || authorization.FormulaActor.ProfileID != "profile-current" ||
		authorization.FormulaActor.ProfileName != "Session Profile" || authorization.FormulaActor.Name != "Session Actor" ||
		authorization.FormulaActor.RoleID == nil || *authorization.FormulaActor.RoleID != roleID ||
		authorization.FormulaActor.RoleName == nil || *authorization.FormulaActor.RoleName != "Session Role" {
		t.Fatalf("current authorization = %#v, want current Profile", authorization)
	}
	if permissionRepo.profileID != "profile-current" {
		t.Fatalf("effective permission profile = %q, want profile-current", permissionRepo.profileID)
	}
	if want := []string{"user", "profile", "permission-state", "effective-permissions"}; !reflect.DeepEqual(order, want) {
		t.Fatalf("authorization order = %v, want %v", order, want)
	}

	// Session revocation protects the next request, but an already-admitted
	// command may race a Profile reassignment. It must fail closed instead of
	// combining current grants with stale session-bound formula identity.
	order = order[:0]
	userRepo.user.ProfileID = "profile-reassigned"
	authorization, authorized, err = policy.AuthorizePermissionSetAuthoring(
		context.Background(), permissionAuthoringPermissionStateVersionForTest(t, 7), sessionActor,
	)
	if err != nil || authorized || authorization != nil {
		t.Fatalf("authorization after Profile reassignment = %#v, %t, %v", authorization, authorized, err)
	}
	if want := []string{"user"}; !reflect.DeepEqual(order, want) {
		t.Fatalf("reassignment authorization order = %v, want %v", order, want)
	}

	order = order[:0]
	userRepo.user.ProfileID = "profile-current"
	permissionRepo.effective[string(constants.PermCustomizeApplication)] = true
	_, authorized, err = policy.AuthorizeSystemPermissionAuthoring(
		context.Background(), permissionAuthoringPermissionStateVersionForTest(t, 7), sessionActor,
		string(constants.PermCustomizeApplication),
	)
	if err != nil || !authorized {
		t.Fatalf("CustomizeApplication transaction-current authorization = %t, %v", authorized, err)
	}
	if want := []string{"user", "profile", "permission-state", "effective-permissions"}; !reflect.DeepEqual(order, want) {
		t.Fatalf("generic authorization order = %v, want %v", order, want)
	}

	order = order[:0]
	permissionRepo.profile.IsActive = false
	authorization, authorized, err = policy.AuthorizeSystemPermissionAuthoring(
		context.Background(), permissionAuthoringPermissionStateVersionForTest(t, 7), sessionActor,
		string(constants.PermCustomizeApplication),
	)
	if err != nil || authorized || authorization != nil || !reflect.DeepEqual(order, []string{"user", "profile"}) {
		t.Fatalf("inactive Profile authorization = %#v, %t, %v, order=%v", authorization, authorized, err, order)
	}
	permissionRepo.profile.IsActive = true

	order = order[:0]
	userRepo.user.IsActive = false
	authorization, authorized, err = policy.AuthorizeSystemPermissionAuthoring(
		context.Background(), permissionAuthoringPermissionStateVersionForTest(t, 7), sessionActor,
		string(constants.PermCustomizeApplication),
	)
	if err != nil || authorized || authorization != nil || !reflect.DeepEqual(order, []string{"user"}) {
		t.Fatalf("deactivated authorization = %#v, %t, %v, order=%v", authorization, authorized, err, order)
	}

	order = order[:0]
	userRepo.user.IsActive = true
	permissionRepo.loadErr = errors.New("permission repository unavailable")
	authorization, authorized, err = policy.AuthorizeSystemPermissionAuthoring(
		context.Background(), permissionAuthoringPermissionStateVersionForTest(t, 7), sessionActor,
		string(constants.PermCustomizeApplication),
	)
	if !errors.Is(err, permissionRepo.loadErr) || authorized || authorization != nil ||
		!reflect.DeepEqual(order, []string{"user", "profile", "permission-state", "effective-permissions"}) {
		t.Fatalf("repository failure = %#v, %t, %v, order=%v", authorization, authorized, err, order)
	}
}

type permissionAuthoringCurrentPermissionRepo struct {
	ports.PermissionRepository
	order     *[]string
	effective map[string]bool
	profile   *models.Profile
	profileID string
	version   permissionstatev1.Version
	loadErr   error
}

func (repo *permissionAuthoringCurrentPermissionRepo) LockProfileLifecycles(
	_ context.Context,
	profileIDs []string,
) ([]*models.Profile, error) {
	*repo.order = append(*repo.order, "profile")
	if len(profileIDs) != 1 || repo.profile == nil || repo.profile.ID != profileIDs[0] {
		return nil, nil
	}
	clone := *repo.profile
	return []*models.Profile{&clone}, nil
}

func (repo *permissionAuthoringCurrentPermissionRepo) GetEffectiveSystemPermissions(
	_ context.Context,
	_ string,
	profileID string,
) (map[string]bool, error) {
	*repo.order = append(*repo.order, "effective-permissions")
	repo.profileID = profileID
	return repo.effective, nil
}

func (repo *permissionAuthoringCurrentPermissionRepo) GetEffectiveCustomPermissions(
	context.Context,
	string,
	string,
) (map[string]bool, error) {
	return map[string]bool{}, nil
}

func (repo *permissionAuthoringCurrentPermissionRepo) LoadCurrentEffectivePermissionSnapshot(
	_ context.Context,
	subject permissionstatev1.Subject,
) (permissionstatev1.EffectiveSnapshot, error) {
	*repo.order = append(*repo.order, "effective-permissions")
	repo.profileID = subject.ProfileID()
	if repo.loadErr != nil {
		return permissionstatev1.EffectiveSnapshot{}, repo.loadErr
	}
	permissions := make([]string, 0, len(repo.effective))
	for permission, enabled := range repo.effective {
		if enabled {
			permissions = append(permissions, permission)
		}
	}
	return permissionstatev1.NewEffectiveSnapshot(
		repo.version,
		permissions,
		nil,
	)
}

type permissionAuthoringCurrentUserRepo struct {
	ports.UserRepository
	order *[]string
	user  *models.User
}

func (repo *permissionAuthoringCurrentUserRepo) GetUserByID(context.Context, string) (*models.User, error) {
	*repo.order = append(*repo.order, "user")
	return repo.user, nil
}

func (repo *permissionAuthoringCurrentUserRepo) GetProfileNameByID(context.Context, string) (string, error) {
	*repo.order = append(*repo.order, "profile-name")
	return "Current Profile", nil
}

func (repo *permissionAuthoringCurrentUserRepo) GetRoleNameByID(context.Context, string) (string, error) {
	*repo.order = append(*repo.order, "role-name")
	return "Current Role", nil
}

type permissionAuthoringCurrentStateFence struct {
	order *[]string
}

func (fence *permissionAuthoringCurrentStateFence) LockPermissionStateAtLeast(
	_ context.Context,
	floor permissionstatev1.Version,
) (permissionstatev1.Version, error) {
	*fence.order = append(*fence.order, "permission-state")
	return floor, nil
}
