package records

import (
	"context"
	"fmt"
	"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"
)

type recordWriteValidationPhase = contract.RecordWriteValidationPhase

const (
	recordWriteValidationBeforeCreate  recordWriteValidationPhase = contract.RecordWriteValidationBeforeCreate
	recordWriteValidationBeforeUpdate  recordWriteValidationPhase = contract.RecordWriteValidationBeforeUpdate
	recordWriteValidationBeforeDelete  recordWriteValidationPhase = contract.RecordWriteValidationBeforeDelete
	recordWriteValidationBeforeRestore recordWriteValidationPhase = contract.RecordWriteValidationBeforeRestore
	recordWriteValidationBeforePurge   recordWriteValidationPhase = contract.RecordWriteValidationBeforePurge
)

type recordWriteValidationEvent struct {
	ObjectName  string
	RecordID    string
	Record      models.SObject
	OldRecord   models.SObject
	CurrentUser *models.UserSession

	PendingCreates []recordWriteValidationPendingCreate
}

type recordWriteValidationPendingCreate struct {
	RecordID string
	Record   models.SObject
}

type recordWriteValidator func(context.Context, recordWriteValidationEvent) error

type recordWriteValidators struct {
	capabilityBeforeCreate  map[string]recordWriteValidator
	capabilityBeforeUpdate  map[string]recordWriteValidator
	capabilityBeforeDelete  map[string]recordWriteValidator
	capabilityBeforeRestore map[string]recordWriteValidator
	capabilityBeforePurge   map[string]recordWriteValidator
}

func newRecordWriteValidators() *recordWriteValidators {
	return &recordWriteValidators{
		capabilityBeforeCreate:  make(map[string]recordWriteValidator),
		capabilityBeforeUpdate:  make(map[string]recordWriteValidator),
		capabilityBeforeDelete:  make(map[string]recordWriteValidator),
		capabilityBeforeRestore: make(map[string]recordWriteValidator),
		capabilityBeforePurge:   make(map[string]recordWriteValidator),
	}
}

func recordWriteValidatorCapabilityKey(capability string) string {
	return strings.ToLower(strings.TrimSpace(capability))
}

func (v *recordWriteValidators) tryRegisterCapability(capability string, phase recordWriteValidationPhase, validator recordWriteValidator) error {
	if v == nil || validator == nil {
		return fmt.Errorf("record write validator registration requires a non-nil registry and validator")
	}
	key := recordWriteValidatorCapabilityKey(capability)
	if key == "" {
		return fmt.Errorf("record write validator registration requires a capability")
	}
	registerOne := func(validators map[string]recordWriteValidator) error {
		if _, exists := validators[key]; exists {
			return fmt.Errorf("duplicate record write validator for %s capability %q", phase, capability)
		}
		validators[key] = validator
		return nil
	}
	switch phase {
	case recordWriteValidationBeforeCreate:
		return registerOne(v.capabilityBeforeCreate)
	case recordWriteValidationBeforeUpdate:
		return registerOne(v.capabilityBeforeUpdate)
	case recordWriteValidationBeforeDelete:
		return registerOne(v.capabilityBeforeDelete)
	case recordWriteValidationBeforeRestore:
		return registerOne(v.capabilityBeforeRestore)
	case recordWriteValidationBeforePurge:
		return registerOne(v.capabilityBeforePurge)
	default:
		return fmt.Errorf("unsupported record write validation phase %q for capability %q", phase, capability)
	}
}

func (v *recordWriteValidators) runCapabilities(ctx context.Context, phase recordWriteValidationPhase, event recordWriteValidationEvent, capabilities []string) error {
	if v == nil || len(capabilities) == 0 {
		return nil
	}
	var validators map[string]recordWriteValidator
	switch phase {
	case recordWriteValidationBeforeCreate:
		validators = v.capabilityBeforeCreate
	case recordWriteValidationBeforeUpdate:
		validators = v.capabilityBeforeUpdate
	case recordWriteValidationBeforeDelete:
		validators = v.capabilityBeforeDelete
	case recordWriteValidationBeforeRestore:
		validators = v.capabilityBeforeRestore
	case recordWriteValidationBeforePurge:
		validators = v.capabilityBeforePurge
	default:
		return fmt.Errorf("unsupported record write validation phase %q for %s", phase, event.ObjectName)
	}
	seen := make(map[string]struct{}, len(capabilities))
	for _, capability := range capabilities {
		key := recordWriteValidatorCapabilityKey(capability)
		if key == "" {
			continue
		}
		if _, exists := seen[key]; exists {
			continue
		}
		seen[key] = struct{}{}
		validator := validators[key]
		if validator == nil {
			return fmt.Errorf("missing record write validator for %s capability %q on %s", phase, capability, event.ObjectName)
		}
		if err := validator(ctx, event); err != nil {
			return err
		}
	}
	return nil
}

func (ps *PersistenceService) RegisterRecordWriteValidator(reg contract.RecordWriteValidatorRegistration) error {
	if ps == nil {
		return fmt.Errorf("record write validator registration requires a persistence service")
	}
	if reg.Validator == nil {
		return fmt.Errorf("record write validator registration requires a non-nil validator")
	}
	if ps.writeValidators == nil {
		ps.writeValidators = newRecordWriteValidators()
	}
	return ps.writeValidators.tryRegisterCapability(reg.Capability, reg.Phase, func(ctx context.Context, event recordWriteValidationEvent) error {
		return reg.Validator(ctx, contract.RecordWriteValidationEvent{
			ObjectName:     event.ObjectName,
			RecordID:       event.RecordID,
			Record:         contractRecordFromSObject(event.Record),
			OldRecord:      contractRecordFromSObject(event.OldRecord),
			CurrentUser:    event.CurrentUser,
			PendingCreates: contractPendingCreatesFromRecordWriteValidation(event.PendingCreates),
		})
	})
}

func (ps *PersistenceService) runRecordWriteValidators(ctx context.Context, phase recordWriteValidationPhase, event recordWriteValidationEvent) error {
	if ps == nil {
		return nil
	}
	var capabilities []string
	var schema *models.ObjectMetadata
	if ps.metadata != nil {
		if schema = ps.metadata.GetSchema(ctx, event.ObjectName); schema != nil {
			capabilities = schema.RecordWriteValidationCapabilitiesForPhase(string(phase))
		}
	}
	if schema != nil {
		requiredRules := schema.RecordWriteValidationRequiredValidationRulesForPhase(string(phase))
		if len(requiredRules) > 0 {
			rules := make([]*models.ValidationRule, 0)
			seenObjects := make(map[string]struct{}, len(requiredRules))
			for _, req := range requiredRules {
				objectAPIName := strings.TrimSpace(req.ObjectAPIName)
				if objectAPIName == "" {
					continue
				}
				key := strings.ToLower(objectAPIName)
				if _, seen := seenObjects[key]; seen {
					continue
				}
				seenObjects[key] = struct{}{}
				objectRules, err := ps.metadata.GetValidationRulesForMutation(ctx, objectAPIName)
				if err != nil {
					return appErrors.NewInternalError("load mutation validation rules", err)
				}
				rules = append(rules, objectRules...)
			}
			if missing := schema.RecordWriteValidationMissingRequiredValidationRulesForPhase(string(phase), rules); len(missing) > 0 {
				return fmt.Errorf("missing required validation rule metadata for %s: required rules %v not active source-backed for %s", event.ObjectName, missing, phase)
			}
		}
	}
	// Fail closed at capability granularity: every capability the managed
	// declaration requires for (object, phase) must be present in the
	// effective schema, even when other validators remain for the same phase.
	if missing := securityWriteValidationMissingRequiredCapabilities(event.ObjectName, phase, capabilities); len(missing) > 0 {
		return fmt.Errorf("missing record write validation metadata for security object %s: required capabilities %v not declared for %s", event.ObjectName, missing, phase)
	}
	if len(capabilities) == 0 {
		return nil
	}
	validators := ps.writeValidators
	if validators == nil {
		validators = newRecordWriteValidators()
	}
	return validators.runCapabilities(ctx, phase, event, capabilities)
}

func recordWriteValidationPendingCreatesFromSObjects(records []models.SObject) []recordWriteValidationPendingCreate {
	if len(records) == 0 {
		return nil
	}
	pending := make([]recordWriteValidationPendingCreate, 0, len(records))
	for _, record := range records {
		pending = append(pending, recordWriteValidationPendingCreate{
			RecordID: strings.TrimSpace(stringField(record, constants.FieldID)),
			Record:   record,
		})
	}
	return pending
}

func contractPendingCreatesFromRecordWriteValidation(pending []recordWriteValidationPendingCreate) []contract.RecordWriteValidationPendingCreate {
	if len(pending) == 0 {
		return nil
	}
	out := make([]contract.RecordWriteValidationPendingCreate, 0, len(pending))
	for _, row := range pending {
		out = append(out, contract.RecordWriteValidationPendingCreate{
			RecordID: row.RecordID,
			Record:   contractRecordFromSObject(row.Record),
		})
	}
	return out
}
