package services

import (
	"context"
	"errors"
	"fmt"

	pkgerrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/formulastorage"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/rs/zerolog/log"
)

func (sm *ServiceManager) MetadataAuthoringContract() contract.MetadataAuthoring {
	if sm == nil {
		return nil
	}
	if sm.NeutralMetadataAuthoring != nil {
		return sm.NeutralMetadataAuthoring
	}
	if sm.Metadata == nil {
		return nil
	}
	return sm
}

func (sm *ServiceManager) ListSchemasForAuthoring(ctx context.Context) []*models.ObjectMetadata {
	if sm == nil {
		return nil
	}
	return sm.GetSchemas(ctx)
}

func (sm *ServiceManager) GetEffectiveSchemaForAuthoring(ctx context.Context, apiName string, user *models.UserSession) *models.ObjectMetadata {
	if sm == nil || sm.Metadata == nil || sm.Permissions == nil {
		return nil
	}
	schema, _ := sm.GetEffectiveSchema(ctx, apiName, user)
	return schema
}

func (sm *ServiceManager) SharingParentRelationship(ctx context.Context, apiName string) *models.Relationship {
	if sm == nil || sm.Metadata == nil {
		return nil
	}
	return sm.Metadata.GetSharingParentRelationship(ctx, apiName)
}

func (sm *ServiceManager) CreateSchema(ctx context.Context, req contract.MetadataSchemaCreateRequest) (*models.ObjectMetadata, error) {
	if sm == nil || sm.Metadata == nil || req.Schema == nil {
		return nil, pkgerrors.NewInternalError("metadata authoring service unavailable", nil)
	}
	schema := req.Schema
	if req.AppID != nil && *req.AppID != "" {
		schema.AppID = req.AppID
	}
	if err := sm.Metadata.CreateSchema(ctx, schema); err != nil {
		return nil, metadataAuthoringError(err)
	}
	if err := sm.provisionCreatedSchemaPermissions(ctx, schema); err != nil {
		return nil, err
	}
	return schema, nil
}

func (sm *ServiceManager) UpdateSchema(ctx context.Context, apiName string, updates *models.ObjectMetadata) error {
	if sm == nil || sm.Metadata == nil {
		return metadataAuthoringInternal("metadata authoring service unavailable")
	}
	if err := sm.Metadata.UpdateSchema(ctx, apiName, updates); err != nil {
		return metadataAuthoringError(err)
	}
	return nil
}

func (sm *ServiceManager) DeleteSchema(ctx context.Context, apiName string) error {
	if sm == nil || sm.Metadata == nil {
		return metadataAuthoringInternal("metadata authoring service unavailable")
	}
	if err := sm.Metadata.DeleteSchema(ctx, apiName); err != nil {
		return metadataAuthoringError(err)
	}
	return nil
}

func (sm *ServiceManager) RefreshAuthoringMetadataCache(ctx context.Context) error {
	if sm == nil {
		return metadataAuthoringInternal("service manager not configured")
	}
	return metadataAuthoringError(sm.RefreshMetadataCache(ctx))
}

func (sm *ServiceManager) CreateField(ctx context.Context, objectAPIName string, field *models.FieldMetadata) error {
	if sm == nil || sm.Metadata == nil {
		return metadataAuthoringInternal("metadata authoring service unavailable")
	}
	if err := sm.Metadata.CreateField(ctx, objectAPIName, field); err != nil {
		return metadataAuthoringError(err)
	}
	return sm.finalizeCreatedFields(ctx, objectAPIName, []*models.FieldMetadata{field})
}

func (sm *ServiceManager) BatchCreateFields(ctx context.Context, objectAPIName string, fields []models.FieldMetadata) ([]*models.FieldMetadata, error) {
	if sm == nil || sm.Metadata == nil {
		return nil, metadataAuthoringInternal("metadata authoring service unavailable")
	}
	created, err := sm.Metadata.BatchCreateFields(ctx, objectAPIName, fields)
	if err != nil {
		return nil, metadataAuthoringError(err)
	}
	if err := sm.finalizeCreatedFields(ctx, objectAPIName, created); err != nil {
		return nil, err
	}
	return created, nil
}

func (sm *ServiceManager) UpdateField(ctx context.Context, objectAPIName string, fieldAPIName string, updates *models.FieldMetadata, presence contract.FieldPatchPresence) error {
	if sm == nil || sm.Metadata == nil {
		return metadataAuthoringInternal("metadata authoring service unavailable")
	}
	return metadataAuthoringError(sm.Metadata.UpdateFieldWithPatchPresence(
		ctx,
		objectAPIName,
		fieldAPIName,
		updates,
		presence.RequiredProvided,
		presence.UniqueProvided,
		presence.TrackHistoryProvided,
		presence.ReadProtectionPolicyProvided,
	))
}

func (sm *ServiceManager) DeleteField(ctx context.Context, objectAPIName string, fieldAPIName string) error {
	if sm == nil || sm.Metadata == nil {
		return metadataAuthoringInternal("metadata authoring service unavailable")
	}
	return metadataAuthoringError(sm.Metadata.DeleteField(ctx, objectAPIName, fieldAPIName))
}

func (sm *ServiceManager) ListValidationRules(ctx context.Context, objectAPIName string) []models.ValidationRule {
	if sm == nil || sm.Metadata == nil {
		return nil
	}
	rules := sm.Metadata.GetValidationRules(ctx, objectAPIName)
	out := make([]models.ValidationRule, 0, len(rules))
	for _, rule := range rules {
		if rule == nil {
			continue
		}
		out = append(out, *rule)
	}
	return out
}

func (sm *ServiceManager) CreateValidationRule(ctx context.Context, rule *models.ValidationRule) error {
	if sm == nil || sm.Metadata == nil {
		return metadataAuthoringInternal("metadata authoring service unavailable")
	}
	return metadataAuthoringError(sm.Metadata.CreateValidationRule(ctx, rule))
}

func (sm *ServiceManager) UpdateValidationRule(ctx context.Context, id string, updates *models.ValidationRule) error {
	if sm == nil || sm.Metadata == nil {
		return metadataAuthoringInternal("metadata authoring service unavailable")
	}
	return metadataAuthoringError(sm.Metadata.UpdateValidationRule(ctx, id, updates))
}

func (sm *ServiceManager) DeleteValidationRule(ctx context.Context, id string) error {
	if sm == nil || sm.Metadata == nil {
		return metadataAuthoringInternal("metadata authoring service unavailable")
	}
	return metadataAuthoringError(sm.Metadata.DeleteValidationRule(ctx, id))
}

func (sm *ServiceManager) CreateAction(ctx context.Context, action *models.ActionMetadata) error {
	if sm == nil || sm.Metadata == nil {
		return metadataAuthoringInternal("metadata authoring service unavailable")
	}
	return metadataAuthoringError(sm.Metadata.CreateAction(ctx, action))
}

func (sm *ServiceManager) CreateLayoutAssignment(ctx context.Context, assignment *models.LayoutAssignment) error {
	if sm == nil || sm.Metadata == nil {
		return metadataAuthoringInternal("metadata authoring service unavailable")
	}
	return metadataAuthoringError(sm.Metadata.CreateAssignment(ctx, assignment))
}

func (sm *ServiceManager) provisionCreatedSchemaPermissions(ctx context.Context, schema *models.ObjectMetadata) error {
	if sm == nil || sm.Permissions == nil || schema == nil {
		return nil
	}
	if err := sm.Permissions.SeedPermissionsForObject(ctx, schema.APIName); err != nil {
		log.Error().Err(err).Str("object", schema.APIName).
			Msg("schema created but permission provisioning failed; rolling schema back")
		if rollbackErr := sm.Metadata.DeleteSchema(ctx, schema.APIName); rollbackErr != nil {
			log.Error().Err(rollbackErr).Str("object", schema.APIName).
				Msg("schema permission rollback failed; object remains inconsistent")
		}
		return &contract.MetadataAuthoringError{
			Kind:    contract.MetadataAuthoringErrorInternal,
			Message: "failed to provision permissions for new object",
		}
	}
	sm.Permissions.InvalidateCache()
	return nil
}

func metadataAuthoringInternal(message string) *contract.MetadataAuthoringError {
	return &contract.MetadataAuthoringError{
		Kind:    contract.MetadataAuthoringErrorInternal,
		Message: message,
	}
}

func metadataAuthoringError(err error) error {
	if err == nil {
		return nil
	}
	if pkgerrors.IsInternal(err) {
		log.Error().Err(err).Msg("metadata authoring internal error: sanitized for client")
		return &contract.MetadataAuthoringError{
			Kind:    contract.MetadataAuthoringErrorInternal,
			Message: "An internal server error occurred. Contact your system administrator.",
		}
	}
	var domainWire *pkgerrors.DomainWireError
	if errors.As(err, &domainWire) {
		return err
	}
	var validation *pkgerrors.ValidationError
	if errors.As(err, &validation) {
		return &contract.MetadataAuthoringError{
			Kind:    contract.MetadataAuthoringErrorValidation,
			Field:   validation.Field,
			Message: validation.Error(),
		}
	}
	var notFound *pkgerrors.NotFoundError
	if errors.As(err, &notFound) {
		return &contract.MetadataAuthoringError{
			Kind:    contract.MetadataAuthoringErrorNotFound,
			Message: notFound.Error(),
		}
	}
	if pkgerrors.IsPermission(err) {
		return &contract.MetadataAuthoringError{
			Kind:    contract.MetadataAuthoringErrorForbidden,
			Message: err.Error(),
		}
	}
	if pkgerrors.IsConflict(err) {
		return &contract.MetadataAuthoringError{
			Kind:    contract.MetadataAuthoringErrorConflict,
			Message: err.Error(),
		}
	}
	log.Error().Err(err).Msg("metadata authoring server error: sanitized for client")
	return &contract.MetadataAuthoringError{
		Kind:    contract.MetadataAuthoringErrorServer,
		Message: "An internal server error occurred. Contact your system administrator.",
	}
}

func (sm *ServiceManager) provisionCreatedFieldPermissions(ctx context.Context, objectAPIName string, fieldNames []string) error {
	if sm == nil || sm.Permissions == nil || len(fieldNames) == 0 {
		return nil
	}
	if err := sm.Permissions.SeedFieldPermissionsForFields(ctx, objectAPIName, fieldNames); err != nil {
		return err
	}
	sm.Permissions.InvalidateCache()
	return nil
}

func metadataFieldNames(fields []*models.FieldMetadata) []string {
	result := make([]string, 0, len(fields))
	for _, field := range fields {
		if field == nil || field.APIName == "" {
			continue
		}
		result = append(result, field.APIName)
	}
	return result
}

func metadataRollbackFieldNames(fields []*models.FieldMetadata) []string {
	result := make([]string, 0, len(fields)*2)
	seen := make(map[string]bool, len(fields)*2)
	for _, field := range fields {
		if field == nil || field.APIName == "" {
			continue
		}
		if !seen[field.APIName] {
			seen[field.APIName] = true
			result = append(result, field.APIName)
		}
		if field.Type == constants.FieldTypeReference && len(field.ReferenceTo) > 1 {
			typeField := metadata.GetPolymorphicTypeColumnName(field.APIName)
			if !seen[typeField] {
				seen[typeField] = true
				result = append(result, typeField)
			}
		}
	}
	return result
}

func metadataHasActiveFormulaFields(fields []*models.FieldMetadata) bool {
	for _, field := range fields {
		if field == nil {
			continue
		}
		if formulastorage.IsActiveComputedField(field) {
			return true
		}
	}
	return false
}

func (sm *ServiceManager) rollbackCreatedFields(ctx context.Context, objectAPIName string, fields []*models.FieldMetadata) {
	if sm == nil || sm.Metadata == nil {
		return
	}
	rollbackNames := metadataRollbackFieldNames(fields)
	for _, fieldName := range rollbackNames {
		if rollbackErr := sm.Metadata.DeleteField(ctx, objectAPIName, fieldName); rollbackErr != nil {
			log.Error().Err(rollbackErr).Str("object", objectAPIName).Str("field", fieldName).
				Msg("field rollback failed; field remains inconsistent")
		}
	}
	if sm.Permissions != nil {
		if err := sm.Permissions.DeleteFieldPermissionsForFields(ctx, objectAPIName, rollbackNames); err != nil {
			log.Error().Err(err).Str("object", objectAPIName).
				Msg("field rollback failed to cleanup field permissions")
		} else {
			sm.Permissions.InvalidateCache()
		}
	}
	if metadataHasActiveFormulaFields(fields) && sm.FormulaViewSvc != nil {
		if err := sm.FormulaViewSvc.RebuildView(ctx, objectAPIName); err != nil {
			log.Error().Err(err).Str("object", objectAPIName).
				Msg("field rollback failed to repair formula view")
		}
	}
}

func (sm *ServiceManager) rebuildActiveFormulaView(ctx context.Context, objectAPIName string, fields []*models.FieldMetadata) error {
	if sm == nil || sm.FormulaViewSvc == nil || !metadataHasActiveFormulaFields(fields) {
		return nil
	}
	if err := sm.FormulaViewSvc.RebuildView(ctx, objectAPIName); err != nil {
		return fmt.Errorf("rebuild formula view for %s: %w", objectAPIName, err)
	}
	return nil
}

func (sm *ServiceManager) finalizeCreatedFields(ctx context.Context, objectAPIName string, fields []*models.FieldMetadata) error {
	if err := sm.rebuildActiveFormulaView(ctx, objectAPIName, fields); err != nil {
		sm.rollbackCreatedFields(ctx, objectAPIName, fields)
		return &contract.MetadataAuthoringError{
			Kind:    contract.MetadataAuthoringErrorInternal,
			Message: "failed to rebuild formula view for new field",
		}
	}
	if err := sm.provisionCreatedFieldPermissions(ctx, objectAPIName, metadataFieldNames(fields)); err != nil {
		sm.rollbackCreatedFields(ctx, objectAPIName, fields)
		return &contract.MetadataAuthoringError{
			Kind:    contract.MetadataAuthoringErrorInternal,
			Message: "failed to provision permissions for new field",
		}
	}
	return nil
}
