package records

import (
	"context"
	"fmt"
	"strings"

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

type recordWriteCanonicalizationEvent struct {
	ObjectName       string
	RecordID         string
	Record           models.SObject
	OldRecord        models.SObject
	EffectiveUpdates models.SObject
	CurrentUser      *models.UserSession
}

type recordWriteCanonicalizer func(context.Context, recordWriteCanonicalizationEvent) error

type recordWriteCanonicalizers struct {
	capabilityBeforeCreate  map[string]recordWriteCanonicalizer
	capabilityBeforeUpdate  map[string]recordWriteCanonicalizer
	capabilityBeforeRestore map[string]recordWriteCanonicalizer
}

func newRecordWriteCanonicalizers() *recordWriteCanonicalizers {
	return &recordWriteCanonicalizers{
		capabilityBeforeCreate:  make(map[string]recordWriteCanonicalizer),
		capabilityBeforeUpdate:  make(map[string]recordWriteCanonicalizer),
		capabilityBeforeRestore: make(map[string]recordWriteCanonicalizer),
	}
}

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

func (c *recordWriteCanonicalizers) tryRegisterCapability(capability string, phase recordWriteValidationPhase, canonicalizer recordWriteCanonicalizer) error {
	if c == nil || canonicalizer == nil {
		return fmt.Errorf("record write canonicalizer registration requires a non-nil registry and canonicalizer")
	}
	key := recordWriteCanonicalizerCapabilityKey(capability)
	if key == "" {
		return fmt.Errorf("record write canonicalizer registration requires a capability")
	}
	registerOne := func(canonicalizers map[string]recordWriteCanonicalizer) error {
		if _, exists := canonicalizers[key]; exists {
			return fmt.Errorf("duplicate record write canonicalizer for %s capability %q", phase, capability)
		}
		canonicalizers[key] = canonicalizer
		return nil
	}
	switch phase {
	case recordWriteValidationBeforeCreate:
		return registerOne(c.capabilityBeforeCreate)
	case recordWriteValidationBeforeUpdate:
		return registerOne(c.capabilityBeforeUpdate)
	case recordWriteValidationBeforeRestore:
		return registerOne(c.capabilityBeforeRestore)
	default:
		return fmt.Errorf("unsupported record write canonicalization phase %q for capability %q", phase, capability)
	}
}

func (c *recordWriteCanonicalizers) runCapabilities(ctx context.Context, phase recordWriteValidationPhase, event recordWriteCanonicalizationEvent, capabilities []string) error {
	if c == nil || len(capabilities) == 0 {
		return nil
	}
	var canonicalizers map[string]recordWriteCanonicalizer
	switch phase {
	case recordWriteValidationBeforeCreate:
		canonicalizers = c.capabilityBeforeCreate
	case recordWriteValidationBeforeUpdate:
		canonicalizers = c.capabilityBeforeUpdate
	case recordWriteValidationBeforeRestore:
		canonicalizers = c.capabilityBeforeRestore
	default:
		return fmt.Errorf("unsupported record write canonicalization phase %q for %s", phase, event.ObjectName)
	}
	seen := make(map[string]struct{}, len(capabilities))
	for _, capability := range capabilities {
		key := recordWriteCanonicalizerCapabilityKey(capability)
		if key == "" {
			continue
		}
		if _, exists := seen[key]; exists {
			continue
		}
		seen[key] = struct{}{}
		canonicalizer := canonicalizers[key]
		if canonicalizer == nil {
			return fmt.Errorf("missing record write canonicalizer for %s capability %q on %s", phase, capability, event.ObjectName)
		}
		if err := canonicalizer(ctx, event); err != nil {
			return err
		}
	}
	return nil
}

func (ps *PersistenceService) RegisterRecordWriteCanonicalizer(reg contract.RecordWriteCanonicalizerRegistration) error {
	if ps == nil {
		return fmt.Errorf("record write canonicalizer registration requires a persistence service")
	}
	if reg.Canonicalizer == nil {
		return fmt.Errorf("record write canonicalizer registration requires a non-nil canonicalizer")
	}
	if ps.writeCanonicalizers == nil {
		ps.writeCanonicalizers = newRecordWriteCanonicalizers()
	}
	return ps.writeCanonicalizers.tryRegisterCapability(reg.Capability, reg.Phase, func(ctx context.Context, event recordWriteCanonicalizationEvent) error {
		contractEvent := contract.RecordWriteCanonicalizationEvent{
			ObjectName:       event.ObjectName,
			RecordID:         event.RecordID,
			Record:           contractRecordFromSObject(event.Record),
			OldRecord:        contractRecordFromSObject(event.OldRecord),
			EffectiveUpdates: contractRecordFromSObject(event.EffectiveUpdates),
			CurrentUser:      event.CurrentUser,
		}
		if err := reg.Canonicalizer(ctx, contractEvent); err != nil {
			return err
		}
		replaceSObjectFromContractRecord(event.Record, contractEvent.Record)
		replaceSObjectFromContractRecord(event.EffectiveUpdates, contractEvent.EffectiveUpdates)
		return nil
	})
}

func (ps *PersistenceService) runRecordWriteCanonicalizers(ctx context.Context, phase recordWriteValidationPhase, event recordWriteCanonicalizationEvent) 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.RecordWriteCanonicalizationCapabilitiesForPhase(string(phase))
		}
	}
	if missing := securityWriteCanonicalizationMissingRequiredCapabilities(event.ObjectName, phase, capabilities); len(missing) > 0 {
		return fmt.Errorf("missing record write canonicalization metadata for security object %s: required capabilities %v not declared for %s", event.ObjectName, missing, phase)
	}
	if len(capabilities) == 0 {
		return nil
	}
	canonicalizers := ps.writeCanonicalizers
	if canonicalizers == nil {
		canonicalizers = newRecordWriteCanonicalizers()
	}
	return canonicalizers.runCapabilities(ctx, phase, event, capabilities)
}

func replaceSObjectFromContractRecord(dst models.SObject, src contract.Record) {
	if dst == nil || src == nil {
		return
	}
	for key := range dst {
		if key == "attributes" {
			continue
		}
		if _, ok := src[key]; !ok {
			delete(dst, key)
		}
	}
	for key, value := range src {
		if key == models.ObjectTypeMetadataKey {
			continue
		}
		dst[key] = value
	}
}
