package records

import (
	"context"
	"errors"
	"fmt"
	"testing"

	"github.com/nexuscrm/kernel/pkg/ports"
	metadataFeature "github.com/nexuscrm/metadata"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recorddefaultsv1"
)

type createDefaultPreviewPermissionRepo struct {
	*stubUpsertPermRepo
	fieldPermissions map[string]*models.FieldPermissions
	objectPermission *models.ObjectPermissions
}

func (repo *createDefaultPreviewPermissionRepo) LoadEffectiveObjectPermission(
	ctx context.Context,
	user *models.UserSession,
	objectAPIName string,
) (*models.ObjectPermissions, error) {
	if repo.objectPermission != nil {
		copy := *repo.objectPermission
		return &copy, nil
	}
	return repo.stubUpsertPermRepo.LoadEffectiveObjectPermission(ctx, user, objectAPIName)
}

func (repo *createDefaultPreviewPermissionRepo) LoadEffectiveFieldPermission(
	_ context.Context,
	_ *models.UserSession,
	_ string,
	fieldAPIName string,
) (*models.FieldPermissions, error) {
	return repo.fieldPermissions[fieldAPIName], nil
}

type createDefaultPreviewQueryService struct {
	ports.QueryService
	result interface{}
	err    error
	calls  int
	query  models.AnalyticsQuery
	user   *models.UserSession
}

func (service *createDefaultPreviewQueryService) RunAnalytics(
	_ context.Context,
	query models.AnalyticsQuery,
	user *models.UserSession,
) (interface{}, error) {
	service.calls++
	service.query = query
	service.user = user
	return service.result, service.err
}

func TestPreviewCreateDefaultsReturnsAuthorizedSiblingMaxAndOwner(t *testing.T) {
	schema := siblingMaxTestSchema(t)
	schema.Fields = append(schema.Fields, models.FieldMetadata{
		APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference,
		ReferenceTo: []string{constants.TableUser}, Createable: true, Updateable: true,
	})
	makeFieldsMap(schema)
	parentSchema := &models.ObjectMetadata{
		APIName: "Parent__c", SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldName, Type: constants.FieldTypeString},
		},
	}
	makeFieldsMap(parentSchema)
	metaSvc := metadataFeature.NewMetadataService(&stubUpsertMetaRepo{
		schemas: []*models.ObjectMetadata{schema, parentSchema},
	}, nil, nil)
	permSvc := permissions.NewPermissionService(&stubUpsertPermRepo{grantCreate: true}, metaSvc, nil)
	repo := &siblingMaxCaptureRepo{
		maxValue: 900,
		captureRecordRepo: captureRecordRepo{findOneFn: func(_ context.Context, tableName, id string) (models.SObject, error) {
			if tableName == "Parent__c" && id == "parent-1" {
				return models.SObject{constants.FieldID: id}, nil
			}
			return nil, nil
		}},
	}
	queryService := &createDefaultPreviewQueryService{result: float64(900)}
	service := &PersistenceService{repo: repo, metadata: metaSvc, permissions: permSvc, querySvc: queryService}
	parentField, parentID := "ParentId", "parent-1"

	fields, err := service.PreviewCreateDefaults(context.Background(), semantic.Request{
		ObjectID: schema.APIName, ParentFieldID: &parentField, ParentRecordID: &parentID,
	}, &models.UserSession{ID: "user-1", Name: "Ada Lovelace", ProfileID: "profile-1"})
	if err != nil {
		t.Fatal(err)
	}
	wantOrder := []string{constants.FieldOwnerID, "SortOrder__c"}
	if len(fields) != len(wantOrder) {
		t.Fatalf("fields = %#v, want %v", fields, wantOrder)
	}
	for index, name := range wantOrder {
		if fields[index].Name != name {
			t.Fatalf("fields[%d].Name = %q, want %q", index, fields[index].Name, name)
		}
	}
	if fields[0].Reference == nil || *fields[0].Reference != "user-1" ||
		fields[0].DisplayValue == nil || *fields[0].DisplayValue != "Ada Lovelace" ||
		fields[0].Provenance != semantic.ProvenanceCurrentPrincipal {
		t.Fatalf("OwnerId default = %#v", fields[0])
	}
	if fields[1].Integer == nil || *fields[1].Integer != 901 ||
		fields[1].Provenance != semantic.ProvenanceSiblingMaxPlusOne {
		t.Fatalf("SortOrder__c default = %#v", fields[1])
	}
	if repo.calls != 0 {
		t.Fatalf("raw sibling-max repository calls = %d, want 0 for user preview", repo.calls)
	}
	if queryService.calls != 1 || queryService.user == nil || queryService.user.ID != "user-1" {
		t.Fatalf("authorized query calls/user = %d/%#v, want one call for user-1", queryService.calls, queryService.user)
	}
	if queryService.query.Operation != "max" || queryService.query.Field == nil ||
		*queryService.query.Field != "SortOrder__c" || len(queryService.query.Criteria) != 1 ||
		queryService.query.Criteria[0].Field != "ParentId" || queryService.query.Criteria[0].Val != "parent-1" {
		t.Fatalf("authorized query = %#v, want MAX(SortOrder__c) for ParentId=parent-1", queryService.query)
	}
}

func TestPreviewCreateDefaultsAcceptsOrdinaryRelationshipContextWithoutSiblingMax(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Child__c", SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{
				APIName: "ParentId", Type: constants.FieldTypeReference,
				ReferenceTo: []string{"Parent__c"}, Createable: true, Updateable: true,
			},
			{
				APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference,
				ReferenceTo: []string{constants.TableUser}, Createable: true, Updateable: true,
			},
		},
	}
	makeFieldsMap(schema)
	metaSvc := metadataFeature.NewMetadataService(&stubUpsertMetaRepo{
		schemas: []*models.ObjectMetadata{schema},
	}, nil, nil)
	permSvc := permissions.NewPermissionService(&stubUpsertPermRepo{grantCreate: true}, metaSvc, nil)
	queryService := &createDefaultPreviewQueryService{result: float64(900)}
	service := &PersistenceService{metadata: metaSvc, permissions: permSvc, querySvc: queryService}
	parentField, parentID := "ParentId", "13034b97-6f96-49da-b7d9-d70742cfffd9"

	fields, err := service.PreviewCreateDefaults(context.Background(), semantic.Request{
		ObjectID: schema.APIName, ParentFieldID: &parentField, ParentRecordID: &parentID,
	}, &models.UserSession{ID: "user-1", Name: "Ada Lovelace", ProfileID: "profile-1"})
	if err != nil {
		t.Fatal(err)
	}
	if len(fields) != 1 || fields[0].Name != constants.FieldOwnerID ||
		fields[0].Provenance != semantic.ProvenanceCurrentPrincipal {
		t.Fatalf("fields = %#v, want only the existing OwnerId default", fields)
	}
	if queryService.calls != 0 {
		t.Fatalf("authorized aggregate queries = %d, want 0 without a sibling-max declaration", queryService.calls)
	}
}

func TestPreviewCreateDefaultsAcceptsPersistedRelationshipRepresentationsWithoutSiblingMax(t *testing.T) {
	for _, test := range []struct {
		name  string
		field models.FieldMetadata
	}{
		{
			name: "legacy lookup alias",
			field: models.FieldMetadata{
				APIName: "ParentId", Type: models.FieldType("lookup"),
				ReferenceTo: []string{"Parent__c"}, Createable: true, Updateable: true,
			},
		},
		{
			name: "explicit master-detail flag",
			field: models.FieldMetadata{
				APIName: "ParentId", Type: models.FieldType("string"), IsMasterDetail: true,
				ReferenceTo: []string{"Parent__c"}, Createable: true, Updateable: true,
			},
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			schema := &models.ObjectMetadata{
				APIName: "Child__c", SharingModel: constants.SharingModelPublicReadWrite,
				Fields: []models.FieldMetadata{test.field},
			}
			makeFieldsMap(schema)
			metaSvc := metadataFeature.NewMetadataService(&stubUpsertMetaRepo{
				schemas: []*models.ObjectMetadata{schema},
			}, nil, nil)
			permSvc := permissions.NewPermissionService(&stubUpsertPermRepo{grantCreate: true}, metaSvc, nil)
			queryService := &createDefaultPreviewQueryService{result: float64(900)}
			service := &PersistenceService{metadata: metaSvc, permissions: permSvc, querySvc: queryService}
			parentField, parentID := "ParentId", "parent-1"

			fields, err := service.PreviewCreateDefaults(context.Background(), semantic.Request{
				ObjectID: schema.APIName, ParentFieldID: &parentField, ParentRecordID: &parentID,
			}, &models.UserSession{ID: "user-1", ProfileID: "profile-1"})
			if err != nil {
				t.Fatal(err)
			}
			if len(fields) != 0 || queryService.calls != 0 {
				t.Fatalf("fields/queries = %#v/%d, want empty defaults without aggregate queries", fields, queryService.calls)
			}
		})
	}
}

func TestPreviewCreateDefaultsRejectsNonRelationshipParentContext(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Child__c", SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{{
			APIName: "Sequence__c", Type: models.FieldType("integer"), Createable: true, Updateable: true,
		}},
	}
	makeFieldsMap(schema)
	metaSvc := metadataFeature.NewMetadataService(&stubUpsertMetaRepo{
		schemas: []*models.ObjectMetadata{schema},
	}, nil, nil)
	permSvc := permissions.NewPermissionService(&stubUpsertPermRepo{grantCreate: true}, metaSvc, nil)
	queryService := &createDefaultPreviewQueryService{result: float64(900)}
	service := &PersistenceService{metadata: metaSvc, permissions: permSvc, querySvc: queryService}
	parentField, parentID := "Sequence__c", "parent-1"

	_, err := service.PreviewCreateDefaults(context.Background(), semantic.Request{
		ObjectID: schema.APIName, ParentFieldID: &parentField, ParentRecordID: &parentID,
	}, &models.UserSession{ID: "user-1", ProfileID: "profile-1"})
	if err == nil {
		t.Fatal("PreviewCreateDefaults accepted a scalar parent context")
	}
	if queryService.calls != 0 {
		t.Fatalf("authorized aggregate queries = %d, want 0 for invalid parent context", queryService.calls)
	}
}

func TestPreviewCreateDefaultsRequiresObjectReadBeforeReturningDefaults(t *testing.T) {
	schema := siblingMaxTestSchema(t)
	schema.Fields = append(schema.Fields, models.FieldMetadata{
		APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference,
		ReferenceTo: []string{constants.TableUser}, Createable: true, Updateable: true,
	})
	makeFieldsMap(schema)
	metaSvc := metadataFeature.NewMetadataService(&stubUpsertMetaRepo{schemas: []*models.ObjectMetadata{schema}}, nil, nil)
	permRepo := &createDefaultPreviewPermissionRepo{
		stubUpsertPermRepo: &stubUpsertPermRepo{grantCreate: true},
		objectPermission: &models.ObjectPermissions{
			PermissionsCreate: true,
			PermissionsRead:   false,
		},
	}
	permSvc := permissions.NewPermissionService(permRepo, metaSvc, nil)
	queryService := &createDefaultPreviewQueryService{result: float64(900)}
	service := &PersistenceService{metadata: metaSvc, permissions: permSvc, querySvc: queryService}

	_, err := service.PreviewCreateDefaults(context.Background(), semantic.Request{ObjectID: schema.APIName}, &models.UserSession{
		ID: "create-only-user", ProfileID: "profile-1",
	})
	if err == nil {
		t.Fatal("PreviewCreateDefaults returned defaults to a create-only principal")
	}
	if queryService.calls != 0 {
		t.Fatalf("authorized query calls = %d, want 0 before object Read authorization", queryService.calls)
	}
}

func TestPreviewCreateDefaultsUsesVisibleSiblingMaximumNotRawMaximum(t *testing.T) {
	schema := siblingMaxTestSchema(t)
	makeFieldsMap(schema)
	parentSchema := &models.ObjectMetadata{
		APIName: "Parent__c", SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{{APIName: constants.FieldID, Type: constants.FieldTypeString}},
	}
	makeFieldsMap(parentSchema)
	metaSvc := metadataFeature.NewMetadataService(&stubUpsertMetaRepo{schemas: []*models.ObjectMetadata{schema, parentSchema}}, nil, nil)
	permSvc := permissions.NewPermissionService(&stubUpsertPermRepo{grantCreate: true}, metaSvc, nil)
	rawRepo := &siblingMaxCaptureRepo{
		maxValue: 999,
		captureRecordRepo: captureRecordRepo{findOneFn: func(_ context.Context, tableName, id string) (models.SObject, error) {
			if tableName == "Parent__c" && id == "parent-1" {
				return models.SObject{constants.FieldID: id}, nil
			}
			return nil, nil
		}},
	}
	queryService := &createDefaultPreviewQueryService{result: float64(4)}
	service := &PersistenceService{repo: rawRepo, metadata: metaSvc, permissions: permSvc, querySvc: queryService}
	parentField, parentID := "ParentId", "parent-1"

	fields, err := service.PreviewCreateDefaults(context.Background(), semantic.Request{
		ObjectID: schema.APIName, ParentFieldID: &parentField, ParentRecordID: &parentID,
	}, &models.UserSession{ID: "user-1", ProfileID: "profile-1"})
	if err != nil {
		t.Fatal(err)
	}
	if len(fields) != 1 || fields[0].Integer == nil || *fields[0].Integer != 5 {
		t.Fatalf("fields = %#v, want visible sibling maximum 4 plus one", fields)
	}
	if rawRepo.calls != 0 || queryService.calls != 1 {
		t.Fatalf("raw/authorized calls = %d/%d, want 0/1", rawRepo.calls, queryService.calls)
	}
}

func TestPreviewCreateDefaultsSuppressesSiblingDefaultWhenRequiredFieldFLSBlocksIt(t *testing.T) {
	for _, test := range []struct {
		name        string
		deniedField string
		readable    bool
		editable    bool
	}{
		{name: "parent not createable", deniedField: "ParentId", readable: true, editable: false},
		{name: "parent not readable", deniedField: "ParentId", readable: false, editable: true},
		{name: "target not createable", deniedField: "SortOrder__c", readable: true, editable: false},
		{name: "target not readable", deniedField: "SortOrder__c", readable: false, editable: true},
	} {
		t.Run(test.name, func(t *testing.T) {
			schema := siblingMaxTestSchema(t)
			schema.Fields = append(schema.Fields, models.FieldMetadata{
				APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference,
				ReferenceTo: []string{constants.TableUser}, Createable: true, Updateable: true,
			})
			makeFieldsMap(schema)
			metaSvc := metadataFeature.NewMetadataService(&stubUpsertMetaRepo{
				schemas: []*models.ObjectMetadata{schema},
			}, nil, nil)
			permRepo := &createDefaultPreviewPermissionRepo{
				stubUpsertPermRepo: &stubUpsertPermRepo{grantCreate: true},
				fieldPermissions: map[string]*models.FieldPermissions{
					test.deniedField: {PermissionsRead: test.readable, PermissionsEdit: test.editable},
				},
			}
			permSvc := permissions.NewPermissionService(permRepo, metaSvc, nil)
			repo := &siblingMaxCaptureRepo{maxValue: 900}
			service := &PersistenceService{repo: repo, metadata: metaSvc, permissions: permSvc}
			parentField, parentID := "ParentId", "parent-1"

			fields, err := service.PreviewCreateDefaults(context.Background(), semantic.Request{
				ObjectID: schema.APIName, ParentFieldID: &parentField, ParentRecordID: &parentID,
			}, &models.UserSession{ID: "user-1", Name: "Ada Lovelace", ProfileID: "profile-1"})
			if err != nil {
				t.Fatal(err)
			}
			if len(fields) != 1 || fields[0].Name != constants.FieldOwnerID ||
				fields[0].Provenance != semantic.ProvenanceCurrentPrincipal {
				t.Fatalf("fields = %#v, want only authorized OwnerId default", fields)
			}
			if repo.calls != 0 {
				t.Fatalf("sibling-max repository calls = %d, want 0 when required-field FLS blocks preview", repo.calls)
			}
		})
	}
}

func TestPreviewCreateDefaultsRejectsTooManyFieldsBeforeAggregateQueries(t *testing.T) {
	schema := siblingMaxTestSchema(t)
	parentFieldName := "ParentId"
	for index := 1; index < semantic.MaxDefaultFields+1; index++ {
		schema.Fields = append(schema.Fields, models.FieldMetadata{
			APIName: fmt.Sprintf("Sequence%02d", index), Type: models.FieldType("integer"),
			Createable: true, Updateable: true, SiblingMaxParentField: &parentFieldName,
		})
	}
	makeFieldsMap(schema)
	metaSvc := metadataFeature.NewMetadataService(&stubUpsertMetaRepo{
		schemas: []*models.ObjectMetadata{schema},
	}, nil, nil)
	permSvc := permissions.NewPermissionService(&stubUpsertPermRepo{grantCreate: true}, metaSvc, nil)
	repo := &siblingMaxCaptureRepo{maxValue: 900}
	service := &PersistenceService{repo: repo, metadata: metaSvc, permissions: permSvc}
	parentField, parentID := "ParentId", "parent-1"

	_, err := service.PreviewCreateDefaults(context.Background(), semantic.Request{
		ObjectID: schema.APIName, ParentFieldID: &parentField, ParentRecordID: &parentID,
	}, &models.UserSession{ID: "user-1", Name: "Ada Lovelace", ProfileID: "profile-1"})
	var failure *semantic.Failure
	if !errors.As(err, &failure) {
		t.Fatalf("error = %T %v, want semantic resource exhaustion", err, err)
	}
	if failure.Category != semantic.FailureResourceExhausted ||
		failure.LimitKind != semantic.LimitFieldCount ||
		failure.Maximum != semantic.MaxDefaultFields {
		t.Fatalf("failure = %#v, want field-count exhaustion at %d", failure, semantic.MaxDefaultFields)
	}
	if repo.calls != 0 {
		t.Fatalf("sibling-max repository calls = %d, want 0 before field-count rejection", repo.calls)
	}
}
