package records

import (
	"context"
	"fmt"
	"reflect"
	"strings"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

const (
	folderWriteCanonicalizeFolderTypeCapability = "platform.folder.write.folder-type-canonicalization"
	folderWriteValidateFolderTypeCapability     = "platform.folder.write.folder-type-permission"
)

func (ps *PersistenceService) registerBuiltInFolderWriteCanonicalizers() {
	mustRegisterFolderWriteCanonicalizer(ps, folderWriteCanonicalizeFolderTypeCapability, contract.RecordWriteValidationBeforeCreate, ps.canonicalizeFolderTypeWriteContract)
	mustRegisterFolderWriteCanonicalizer(ps, folderWriteCanonicalizeFolderTypeCapability, contract.RecordWriteValidationBeforeUpdate, ps.canonicalizeFolderTypeWriteContract)
	mustRegisterFolderWriteCanonicalizer(ps, folderWriteCanonicalizeFolderTypeCapability, contract.RecordWriteValidationBeforeRestore, ps.canonicalizeFolderTypeWriteContract)
}

func (ps *PersistenceService) registerBuiltInFolderWriteValidators() {
	mustRegisterFolderWriteValidator(ps, folderWriteValidateFolderTypeCapability, contract.RecordWriteValidationBeforeCreate, ps.validateFolderTypeWriteContract)
	mustRegisterFolderWriteValidator(ps, folderWriteValidateFolderTypeCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateFolderTypeWriteContract)
	mustRegisterFolderWriteValidator(ps, folderWriteValidateFolderTypeCapability, contract.RecordWriteValidationBeforeDelete, ps.validateFolderTypeWriteContract)
	mustRegisterFolderWriteValidator(ps, folderWriteValidateFolderTypeCapability, contract.RecordWriteValidationBeforeRestore, ps.validateFolderTypeWriteContract)
	mustRegisterFolderWriteValidator(ps, folderWriteValidateFolderTypeCapability, contract.RecordWriteValidationBeforePurge, ps.validateFolderTypeWriteContract)
}

func mustRegisterFolderWriteCanonicalizer(ps *PersistenceService, capability string, phase contract.RecordWriteValidationPhase, canonicalizer contract.RecordWriteCanonicalizer) {
	if ps == nil {
		return
	}
	if err := ps.RegisterRecordWriteCanonicalizer(contract.RecordWriteCanonicalizerRegistration{
		Capability:    capability,
		Phase:         phase,
		Canonicalizer: canonicalizer,
	}); err != nil {
		panic(err)
	}
}

func mustRegisterFolderWriteValidator(ps *PersistenceService, capability string, phase contract.RecordWriteValidationPhase, validator contract.RecordWriteValidator) {
	if ps == nil {
		return
	}
	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: capability,
		Phase:      phase,
		Validator:  validator,
	}); err != nil {
		panic(err)
	}
}

func (ps *PersistenceService) canonicalizeFolderTypeWriteContract(ctx context.Context, event contract.RecordWriteCanonicalizationEvent) error {
	if constants.GetSecurityContext(ctx) >= constants.SystemContext {
		return nil
	}
	if err := ps.requireFolderTypeWritePolicyShape(ctx, event.ObjectName); err != nil {
		return err
	}
	if event.Record == nil {
		return nil
	}

	rawType, hasType := event.Record[constants.FieldType]
	if event.EffectiveUpdates != nil {
		if updateType, ok := event.EffectiveUpdates[constants.FieldType]; ok {
			rawType, hasType = updateType, true
		}
	}
	if !hasType {
		return nil
	}
	oldTypeRaw, hasOldType := event.OldRecord[constants.FieldType]
	if hasOldType && event.EffectiveUpdates != nil {
		if _, explicitlyUpdatingType := event.EffectiveUpdates[constants.FieldType]; !explicitlyUpdatingType &&
			reflect.DeepEqual(event.Record[constants.FieldType], oldTypeRaw) {
			return nil
		}
	}

	folderType := folderTypeAPIName(rawType)
	if folderType == "" {
		return appErrors.NewValidationError(constants.FieldType, "Folder type is required")
	}

	folderAccess := ps.folderAccessService()
	if folderAccess == nil {
		return folderTypeCreateForbidden()
	}
	canonicalFolderType, ok, err := folderAccess.CanonicalFolderTypeAPIName(ctx, folderType)
	if err != nil {
		return folderTypeCreateForbidden()
	}
	if ok {
		folderType = canonicalFolderType
		event.Record[constants.FieldType] = canonicalFolderType
		if event.EffectiveUpdates != nil {
			event.EffectiveUpdates[constants.FieldType] = canonicalFolderType
		}
	}

	oldType := folderTypeAPIName(oldTypeRaw)
	if hasOldType && oldType != "" && !strings.EqualFold(folderType, oldType) &&
		!canCreateAndManageFolderType(ctx, folderAccess, event.CurrentUser, folderType) {
		return folderTypeCreateForbidden()
	}
	return nil
}

func (ps *PersistenceService) validateFolderTypeWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	if constants.GetSecurityContext(ctx) >= constants.SystemContext {
		return nil
	}
	if err := ps.requireFolderTypeWritePolicyShape(ctx, event.ObjectName); err != nil {
		return err
	}

	folderAccess := ps.folderAccessService()
	if folderAccess == nil {
		if len(event.OldRecord) == 0 {
			return folderTypeCreateForbidden()
		}
		return folderTypeManageForbidden()
	}

	if len(event.OldRecord) == 0 {
		folderType := folderTypeAPIName(event.Record[constants.FieldType])
		if folderType == "" {
			return appErrors.NewValidationError(constants.FieldType, "Folder type is required")
		}
		if !canCreateAndManageFolderType(ctx, folderAccess, event.CurrentUser, folderType) {
			return folderTypeCreateForbidden()
		}
		return nil
	}

	existingFolderType := folderTypeAPIName(event.OldRecord[constants.FieldType])
	if existingFolderType == "" || !folderAccess.CanManageFolderType(ctx, event.CurrentUser, existingFolderType) {
		return folderTypeManageForbidden()
	}
	return nil
}

func (ps *PersistenceService) requireFolderTypeWritePolicyShape(ctx context.Context, objectName string) error {
	if ps == nil || ps.metadata == nil {
		return nil
	}
	schema := ps.metadata.GetSchema(ctx, objectName)
	if schema == nil {
		return nil
	}
	if strings.EqualFold(strings.TrimSpace(string(schema.SharingModel)), string(constants.SharingModelFolderSharing)) {
		return nil
	}
	return fmt.Errorf("folder type write policy declared on %s with sharing model %q; want %q", objectName, schema.SharingModel, constants.SharingModelFolderSharing)
}

func (ps *PersistenceService) folderAccessService() folderAccessPolicy {
	if ps == nil || ps.permissions == nil {
		return nil
	}
	return ps.permissions.FolderAccessService()
}

type folderAccessPolicy interface {
	CanonicalFolderTypeAPIName(context.Context, string) (string, bool, error)
	CanCreateFolderType(context.Context, *models.UserSession, string) bool
	CanManageFolderType(context.Context, *models.UserSession, string) bool
}

func canCreateAndManageFolderType(ctx context.Context, folderAccess folderAccessPolicy, user *models.UserSession, folderType string) bool {
	return folderAccess != nil &&
		folderAccess.CanCreateFolderType(ctx, user, folderType) &&
		folderAccess.CanManageFolderType(ctx, user, folderType)
}

func folderTypeAPIName(value interface{}) string {
	if value == nil {
		return ""
	}
	switch v := value.(type) {
	case string:
		return strings.TrimSpace(v)
	default:
		return strings.TrimSpace(fmt.Sprint(v))
	}
}

func folderTypeCreateForbidden() error {
	return appErrors.NewForbiddenError("You do not have permission to create this folder type")
}

func folderTypeManageForbidden() error {
	return appErrors.NewForbiddenError("You do not have permission to manage this folder type")
}
