package permissions

import (
	"context"
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
)

// Repository facts differ by subject; the real authorization and request-cache
// paths must preserve that distinction through an administrative projection.
type subjectPermissionRepo struct {
	contractAdminRepo
	viewerCanInspect bool
	objectReads      map[string]int
}

func (r *subjectPermissionRepo) GetEffectiveSystemPermissions(_ context.Context, userID, _ string) (map[string]bool, error) {
	return map[string]bool{string(constants.PermViewSetup): userID == "viewer" && r.viewerCanInspect}, nil
}

func (r *subjectPermissionRepo) LoadEffectiveObjectPermission(_ context.Context, user *models.UserSession, object string) (*models.ObjectPermissions, error) {
	r.objectReads[user.ID]++
	return &models.ObjectPermissions{
		SobjectType: object, PermissionsRead: true,
		PermissionsCreate: user.ID == "target", PermissionsEdit: user.ID == "viewer",
	}, nil
}

func (r *subjectPermissionRepo) ListEffectiveObjectPermissionsForUser(ctx context.Context, userID, profileID string) ([]models.ObjectPermissions, error) {
	row, err := r.LoadEffectiveObjectPermission(ctx, &models.UserSession{ID: userID, ProfileID: profileID}, "Entry")
	return []models.ObjectPermissions{*row}, err
}

func TestContractPermissionAdminSubjectIsolation(t *testing.T) {
	for _, endpoint := range []string{"complete", "selected", "fields_only"} {
		for _, sharedProfile := range []bool{false, true} {
			t.Run(endpoint+map[bool]string{true: "/shared_profile", false: "/different_profiles"}[sharedProfile], func(t *testing.T) {
				repo := &subjectPermissionRepo{viewerCanInspect: true, objectReads: map[string]int{}}
				viewer := &models.UserSession{ID: "viewer", ProfileID: "viewer-profile"}
				if sharedProfile {
					repo.userProfileID = viewer.ProfileID
				}
				metadata := &flsMetadataStub{schemas: []*models.ObjectMetadata{{
					APIName: "Entry", Fields: []models.FieldMetadata{{APIName: "Name"}},
				}}}
				service := NewPermissionService(repo, metadata, nil)
				admin := NewContractPermissionAdmin(service, nil)
				// Match Runtime ingress. Authorizing the administrative read binds
				// this empty cache to the viewer before the target is projected.
				ctx := WithAccessInputCache(t.Context())
				var got *contract.EffectivePermissionsResult
				var err error
				if endpoint == "complete" {
					got, err = admin.EffectivePermissions(ctx, viewer, "target")
				} else {
					sections := []permissionsemantic.Section{permissionsemantic.SectionFieldPermissions}
					if endpoint == "selected" {
						sections = append(sections, permissionsemantic.SectionObjectPermissions)
					}
					got, err = admin.EffectivePermissionsSelected(ctx, viewer, "target", permissionsemantic.Selection{
						Sections: sections, ResourceTypes: []string{"Entry"},
					})
				}
				if err != nil {
					t.Fatal(err)
				}
				want := contract.FieldAccessProjection{ResourceType: "Entry", FieldName: "Name", CanRead: true, CanCreate: true}
				if len(got.FieldPermissions) != 1 || got.FieldPermissions[0] != want {
					t.Fatalf("target absent-FLS projection = %+v, want %+v", got.FieldPermissions, want)
				}
				if endpoint != "fields_only" && (len(got.ObjectPermissions) != 1 ||
					!got.ObjectPermissions[0].CanRead || !got.ObjectPermissions[0].CanCreate || got.ObjectPermissions[0].CanEdit) {
					t.Fatalf("target object projection = %+v", got.ObjectPermissions)
				}
				// Returning from the target scope must leave the caller's cache
				// usable for its original identity, with no target grants leaking.
				if !service.CheckObjectPermissionWithUser(ctx, "Entry", constants.PermEdit, viewer) ||
					service.CheckObjectPermissionWithUser(ctx, "Entry", constants.PermCreate, viewer) {
					t.Fatal("target projection changed the viewer's permission decisions")
				}
				if repo.objectReads["viewer"] != 1 {
					t.Fatalf("viewer object reads = %d; request cache no longer reuses its own inputs", repo.objectReads["viewer"])
				}
			})
		}
	}
}

func TestContractPermissionAdminSubjectIsolationRequiresViewerAuthorization(t *testing.T) {
	for _, selected := range []bool{false, true} {
		t.Run(map[bool]string{false: "complete", true: "selected"}[selected], func(t *testing.T) {
			repo := &subjectPermissionRepo{objectReads: map[string]int{}}
			service := NewPermissionService(repo, &flsMetadataStub{}, nil)
			admin := NewContractPermissionAdmin(service, nil)
			viewer := &models.UserSession{ID: "viewer", ProfileID: "viewer-profile"}
			ctx := WithAccessInputCache(t.Context())
			var err error
			if selected {
				_, err = admin.EffectivePermissionsSelected(ctx, viewer, "target", permissionsemantic.Selection{
					Sections: []permissionsemantic.Section{permissionsemantic.SectionFieldPermissions}, ResourceTypes: []string{"Entry"},
				})
			} else {
				_, err = admin.EffectivePermissions(ctx, viewer, "target")
			}
			assertPermissionAdminErrorKind(t, err, contract.PermissionAdminErrorForbidden)
			if len(repo.objectReads) != 0 || repo.fullFieldReads != 0 || repo.boundedFieldReads != 0 {
				t.Fatal("unauthorized viewer reached target permission inputs")
			}
		})
	}
}

func TestContractPermissionAdminSelfProjectionKeepsRequestSnapshot(t *testing.T) {
	repo := &subjectPermissionRepo{objectReads: map[string]int{}}
	metadata := &flsMetadataStub{schemas: []*models.ObjectMetadata{{
		APIName: "Entry", Fields: []models.FieldMetadata{{APIName: "Name"}},
	}}}
	service := NewPermissionService(repo, metadata, nil)
	admin := NewContractPermissionAdmin(service, nil)
	viewer := &models.UserSession{ID: "viewer", ProfileID: "viewer-profile"}
	ctx := WithAccessInputCache(t.Context())
	if !service.CheckObjectPermissionWithUser(ctx, "Entry", constants.PermEdit, viewer) {
		t.Fatal("viewer Edit grant is unavailable")
	}
	got, err := admin.EffectivePermissionsSelected(ctx, viewer, "", permissionsemantic.Selection{
		Sections:      []permissionsemantic.Section{permissionsemantic.SectionObjectPermissions, permissionsemantic.SectionFieldPermissions},
		ResourceTypes: []string{"Entry"},
	})
	if err != nil {
		t.Fatal(err)
	}
	want := contract.FieldAccessProjection{ResourceType: "Entry", FieldName: "Name", CanRead: true, CanEdit: true}
	if len(got.FieldPermissions) != 1 || got.FieldPermissions[0] != want || repo.objectReads["viewer"] != 1 {
		t.Fatalf("self projection lost viewer snapshot: fields=%+v reads=%v", got.FieldPermissions, repo.objectReads)
	}
}
