package permissions

import (
	"context"
	"testing"

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

// seedRouteSpyRepo extends saveTestRepo to count BOTH the additive-only
// InsertMissing* path and the overwrite Upsert* path, so a seeding test can
// prove that seedPermissionRows routes EXCLUSIVELY through InsertMissing* (never
// widening an existing row via Upsert). saveTestRepo already counts
// insertMissingObjectPermissionCalls; we add field-insert + upsert counters.
type seedRouteSpyRepo struct {
	saveTestRepo
	insertMissingFieldCalls int
	upsertObjectCalls       int
	upsertFieldCalls        int
}

func (r *seedRouteSpyRepo) InsertMissingFieldPermissions(context.Context, []models.FieldPermissions) error {
	r.insertMissingFieldCalls++
	return nil
}

func (r *seedRouteSpyRepo) UpsertObjectPermission(context.Context, models.ObjectPermissions) error {
	r.upsertObjectCalls++
	return nil
}

func (r *seedRouteSpyRepo) UpsertFieldPermission(context.Context, models.FieldPermissions) error {
	r.upsertFieldCalls++
	return nil
}

// TestRLSPolicySeedIsAdditiveAndSourceLimited pins two capture-and-freeze
// invariants of the permission seeder that L0-D must preserve:
//
//  1. Source-limited: resolvePermRule for an unknown (imported/admin) profile
//     returns the empty PermRule{} — no StandardUser fallback, no default grant.
//  2. Additive-only routing: seedPermissionRows for a known profile writes ONLY
//     through InsertMissing* (both object and field), never through Upsert* — so a
//     default-seed pass can never overwrite or widen an existing (imported/edited)
//     row.
func TestRLSPolicySeedIsAdditiveAndSourceLimited(t *testing.T) {
	ctx := context.Background()

	// (1) Unknown profile => empty rule (no fallback grant).
	if rule := resolvePermRule("00eIMPORTEDprofile", businessSchema()); rule != (PermRule{}) {
		t.Fatalf("unknown profile must resolve to empty PermRule{}, got %+v", rule)
	}

	// (2) Additive-only routing for a KNOWN profile (StandardUser) over a business
	//     schema with a field, so both an ObjectPermissions row and a FieldPermissions
	//     row are produced and must flow through InsertMissing*.
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields:  []models.FieldMetadata{{APIName: "Name"}},
	}
	objPerms, fieldPerms := BuildPermissionsForProfileAndSchema(constants.ProfileStandardUser, "ps_std", schema, false)
	if len(objPerms) == 0 {
		t.Fatalf("StandardUser business schema must yield at least one ObjectPermissions row, got 0")
	}
	if len(fieldPerms) == 0 {
		t.Fatalf("StandardUser (Read/Edit) must yield FieldPermissions rows, got 0")
	}

	repo := &seedRouteSpyRepo{}
	svc := NewPermissionService(repo, &accessDecisionGoldenMetadata{}, nil)

	if err := svc.seedPermissionRows(ctx, objPerms, fieldPerms); err != nil {
		t.Fatalf("seedPermissionRows: %v", err)
	}

	// InsertMissing* must have fired for both planes.
	if repo.insertMissingObjectPermissionCalls == 0 {
		t.Fatalf("seedPermissionRows must route object rows through InsertMissingObjectPermissions (calls=%d)",
			repo.insertMissingObjectPermissionCalls)
	}
	if repo.insertMissingFieldCalls == 0 {
		t.Fatalf("seedPermissionRows must route field rows through InsertMissingFieldPermissions (calls=%d)",
			repo.insertMissingFieldCalls)
	}
	// No overwrite path may fire.
	if repo.upsertObjectCalls != 0 || repo.upsertFieldCalls != 0 {
		t.Fatalf("seedPermissionRows must NOT use Upsert* (widening), got upsertObject=%d upsertField=%d",
			repo.upsertObjectCalls, repo.upsertFieldCalls)
	}

	// ShouldMaterializeFieldPermissionRow gotcha (permission_seeder.go:67): it returns
	// field != nil UNCONDITIONALLY, so a materialized FLS row does NOT imply the field
	// is readable/editable. Pin the current behavior so a future de-skin that changes it
	// is caught here rather than silently.
	if !ShouldMaterializeFieldPermissionRow(&models.FieldMetadata{APIName: "AnythingReadOnly__c"}) {
		t.Fatalf("ShouldMaterializeFieldPermissionRow must currently return true for any non-nil field")
	}
	if ShouldMaterializeFieldPermissionRow(nil) {
		t.Fatalf("ShouldMaterializeFieldPermissionRow(nil) must be false")
	}
}
