package events

import (
	"context"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/nexuscrm/shared/pkg/constants"
	sharedevents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

const (
	internalMetadataKeyMinimalRecordPayload = "minimal_record_payload"
	minimalRecordPayloadValue               = "true"
	minimalRecordCreatedOutboxPayloadEnv    = "NEXUSCRM_OUTBOX_RECORD_CREATED_MINIMAL_PAYLOAD"
)

// RecordPayloadLoader reloads a committed record image for an outbox event
// whose foreground transaction persisted only the durable object/id marker.
type RecordPayloadLoader interface {
	LoadOutboxRecordPayload(ctx context.Context, objectAPIName, recordID string, user *models.UserSession) (models.SObject, error)
}

// MarkMinimalRecordPayload tells OutboxService to persist this event's public
// record payload in object/id form and require worker-side rehydration before
// publishing to normal subscribers.
func MarkMinimalRecordPayload(ctx context.Context) context.Context {
	return context.WithValue(ctx, constants.ContextKey(internalMetadataKeyMinimalRecordPayload), minimalRecordPayloadValue)
}

// PrepareRecordCreatedOutboxPayload owns the durable RecordCreated payload
// contract. When the rollout flag is enabled, the outbox row stores only the
// record Id plus an internal marker; the worker must rehydrate the committed
// row before normal subscribers observe the event.
func PrepareRecordCreatedOutboxPayload(ctx context.Context, payload RecordEventPayload) (context.Context, RecordEventPayload) {
	if !minimalRecordCreatedOutboxPayloadEnabled() {
		return ctx, payload
	}
	recordID := recordIDForMinimalRecordPayload(payload.Record)
	if recordID == "" {
		return ctx, payload
	}
	payload.Record = models.SObject{
		constants.FieldID: recordID,
	}
	return MarkMinimalRecordPayload(ctx), payload
}

func minimalRecordCreatedOutboxPayloadEnabled() bool {
	raw := strings.TrimSpace(os.Getenv(minimalRecordCreatedOutboxPayloadEnv))
	if raw == "" {
		return false
	}
	enabled, err := strconv.ParseBool(raw)
	return err == nil && enabled
}

func recordIDForMinimalRecordPayload(record models.SObject) string {
	if record == nil {
		return ""
	}
	if id, ok := record[constants.FieldID].(string); ok {
		return id
	}
	if raw, ok := record[constants.FieldID]; ok && raw != nil {
		return fmt.Sprint(raw)
	}
	return ""
}

func captureMinimalRecordPayloadInternalMetadata(ctx context.Context, _ map[string]string, internalMetadata map[string]string) internalMetadataCaptureResult {
	if v, _ := ctx.Value(constants.ContextKey(internalMetadataKeyMinimalRecordPayload)).(string); v == minimalRecordPayloadValue {
		internalMetadata[internalMetadataKeyMinimalRecordPayload] = minimalRecordPayloadValue
	}
	return internalMetadataCaptureResult{ctx: ctx}
}

func minimalRecordPayloadMarked(internalMetadata map[string]string) bool {
	return internalMetadata != nil && internalMetadata[internalMetadataKeyMinimalRecordPayload] == minimalRecordPayloadValue
}

func (os *OutboxService) expandMinimalRecordPayload(ctx context.Context, eventType string, payload RecordEventPayload, internalMetadata map[string]string) (RecordEventPayload, error) {
	if !minimalRecordPayloadMarked(internalMetadata) {
		return payload, nil
	}
	if eventType != string(sharedevents.RecordCreated) {
		return payload, nil
	}
	if os == nil || os.recordPayloadLoader == nil {
		return payload, fmt.Errorf("minimal RecordCreated outbox payload requires a record payload loader")
	}
	recordID := payload.Record.GetString(constants.FieldID)
	if payload.ObjectAPIName == "" || recordID == "" {
		return payload, fmt.Errorf("minimal RecordCreated outbox payload missing object or record id")
	}
	record, err := os.recordPayloadLoader.LoadOutboxRecordPayload(ctx, payload.ObjectAPIName, recordID, payload.CurrentUser)
	if err != nil {
		return payload, fmt.Errorf("rehydrate RecordCreated outbox payload %s/%s: %w", payload.ObjectAPIName, recordID, err)
	}
	if record == nil {
		return payload, fmt.Errorf("rehydrate RecordCreated outbox payload %s/%s: record not found", payload.ObjectAPIName, recordID)
	}
	if record.GetString(constants.FieldID) == "" {
		record[constants.FieldID] = recordID
	}
	models.StampObjectType(record, payload.ObjectAPIName)
	payload.Record = record
	return payload, nil
}
