package records

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"strings"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/schemautil"
	infraPersistence "github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// protectedCreateBaseline captures values already admitted or materialized by
// Records before user-authored before-save automation runs. Presence in a final
// image is not itself a new write. Keep an immutable value snapshot so in-place
// edits to structured values cannot alter the comparison baseline.
type protectedCreateBaseline map[string][]byte

func captureProtectedCreateBaseline(schema *models.ObjectMetadata, record models.SObject) (protectedCreateBaseline, error) {
	baseline := make(protectedCreateBaseline)
	if schema == nil {
		return baseline, nil
	}
	normalized := schemautil.NormalizeSObject(schema, cloneRecord(record))
	for _, field := range schema.Fields {
		if strings.TrimSpace(field.WriteProtectionPolicy) != constants.FieldWriteProtectionTrustedOnly {
			continue
		}
		value, exists := normalized[field.APIName]
		if !exists {
			continue
		}
		encoded, err := json.Marshal(value)
		if err != nil {
			return nil, appErrors.NewInternalError("cannot capture protected create value", err)
		}
		baseline[field.APIName] = encoded
	}
	return baseline, nil
}

func (baseline protectedCreateBaseline) validate(ctx context.Context, schema *models.ObjectMetadata, final models.SObject) error {
	// The canonical authority fence must inspect the full image, even if stale
	// live metadata incorrectly calls one of its fields trusted_only.
	if schema != nil {
		if err := infraPersistence.RejectAuthorityOnlyFieldWrites(schema.APIName, schema, final); err != nil {
			return err
		}
	}
	writes := cloneRecord(final)
	for field, before := range baseline {
		value, exists := final[field]
		if !exists {
			writes[field] = nil // Removing a protected default is also a mutation.
			continue
		}
		after, err := json.Marshal(value)
		if err == nil && bytes.Equal(before, after) {
			delete(writes, field)
		}
	}
	// Authority-only fields deliberately receive no baseline exemption.
	return rejectWriteProtectedFieldWrites(ctx, schema, writes, protectedFieldWriteOnCreate)
}

type protectedFieldWritePhase uint8

const (
	protectedFieldWriteOnCreate protectedFieldWritePhase = iota
	protectedFieldWriteOnUpdate
)

func rejectWriteProtectedFieldWrites(ctx context.Context, schema *models.ObjectMetadata, data models.SObject, phase protectedFieldWritePhase) error {
	if schema == nil || len(data) == 0 {
		return nil
	}
	if err := infraPersistence.RejectAuthorityOnlyFieldWrites(schema.APIName, schema, data); err != nil {
		return err
	}
	bypassesProtectedFields := constants.GetSecurityContext(ctx).BypassesProtectedFieldWrites()
	for i := range schema.Fields {
		field := &schema.Fields[i]
		policy := strings.TrimSpace(field.WriteProtectionPolicy)
		if policy == "" {
			continue
		}
		if bypassesProtectedFields && policy != constants.FieldWriteProtectionAuthorityOnly {
			continue
		}
		if phase == protectedFieldWriteOnCreate && policy == constants.FieldWriteProtectionImmutableAfterCreate {
			continue
		}
		if _, exists := data[field.APIName]; exists {
			return appErrors.NewInvalidFieldForWriteError(
				[]string{field.APIName},
				fmt.Sprintf("field '%s' is write-protected by metadata policy %q", field.APIName, policy),
			)
		}
	}
	return nil
}

func isTrustedWriteProtectedFieldWrite(ctx context.Context, schema *models.ObjectMetadata, fieldAPIName string) bool {
	if schema == nil || !constants.GetSecurityContext(ctx).BypassesProtectedFieldWrites() {
		return false
	}
	for i := range schema.Fields {
		field := &schema.Fields[i]
		if !strings.EqualFold(field.APIName, fieldAPIName) {
			continue
		}
		policy := strings.TrimSpace(field.WriteProtectionPolicy)
		return policy != "" && policy != constants.FieldWriteProtectionAuthorityOnly
	}
	return false
}
