package sfcrmappservice

import (
	"context"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"sort"
	"strings"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordmutationv1"
)

// RuntimeMutateFunc is one complete neutral mutation command. The concrete
// transport owner exchanges the end-user bearer for one Runtime delegation;
// no identity or metadata facts are accepted in the business request.
type RuntimeMutateFunc func(context.Context, string, semantic.Request) (semantic.Response, error)

// RuntimeMutationClient owns only the CRM source-JSON projection and command
// identity. Runtime owns Catalog field interpretation, policy, CRUD/FLS/RLS,
// transactions, automation, durable outcome, and worker handoff.
type RuntimeMutationClient struct {
	mutate       RuntimeMutateFunc
	newCommandID func() (string, error)
}

func NewRuntimeMutationClient(mutate RuntimeMutateFunc) (*RuntimeMutationClient, error) {
	if mutate == nil {
		return nil, errors.New("sfcrm AppService: Runtime mutation port is required")
	}
	return &RuntimeMutationClient{mutate: mutate, newCommandID: newMutationCommandID}, nil
}

func (c *RuntimeMutationClient) Mutate(ctx context.Context, bearer, commandID string, useCase semantic.MutationUseCase, mode semantic.BatchMode, operations []semantic.Operation) (semantic.Response, error) {
	if c == nil || c.mutate == nil || c.newCommandID == nil {
		return semantic.Response{}, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	if commandID == "" {
		generated, err := c.newCommandID()
		if err != nil {
			return semantic.Response{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		commandID = generated
	}
	request := semantic.Request{
		CommandID: commandID, BatchMode: mode, UseCase: useCase,
		Operations: append([]semantic.Operation(nil), operations...),
	}
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return semantic.Response{}, err
	}
	response, err := c.mutate(ctx, bearer, request)
	if err != nil {
		return semantic.Response{}, err
	}
	if err := semantic.ValidateResponse(request, response); err != nil {
		return semantic.Response{}, err
	}
	return response, nil
}

// mutationCommandID maps the optional CRM HTTP idempotency identity into the
// neutral Runtime command namespace. The raw product header never crosses the
// L1/L0 boundary. An absent key preserves the existing non-idempotent public
// route behavior; an explicit key is stable across AppService restarts.
func mutationCommandID(values []string) (string, error) {
	if len(values) == 0 {
		return "", nil
	}
	if len(values) != 1 || values[0] == "" || strings.TrimSpace(values[0]) != values[0] {
		return "", errors.New("Idempotency-Key must be one non-empty canonical value")
	}
	digest := sha256.Sum256([]byte(values[0]))
	return "sfcrm-idem-" + hex.EncodeToString(digest[:]), nil
}

func newMutationCommandID() (string, error) {
	var random [16]byte
	if _, err := rand.Read(random[:]); err != nil {
		return "", err
	}
	return "sfcrm-" + hex.EncodeToString(random[:]), nil
}

func mutationFields(source map[string]any, omitID bool) ([]semantic.FieldValue, error) {
	if source == nil {
		return []semantic.FieldValue{}, nil
	}
	names := make([]string, 0, len(source))
	for name := range source {
		if isMutationCarrierField(name) || (omitID && strings.EqualFold(name, "Id")) {
			continue
		}
		names = append(names, name)
	}
	sort.Strings(names)
	fields := make([]semantic.FieldValue, 0, len(names))
	for _, name := range names {
		field, err := mutationField(name, source[name])
		if err != nil {
			return nil, err
		}
		fields = append(fields, field)
	}
	return fields, nil
}

// isMutationCarrierField preserves the existing CRM ingress boundary: record
// carrier metadata describes an in-process value but is never a user-authored
// field. The coarse Runtime request already carries object identity separately,
// and exact decimals are reconstructed from the source JSON number itself.
// Forwarding these keys would turn engine-private sidecars into business fields.
func isMutationCarrierField(name string) bool {
	if strings.EqualFold(name, "attributes") {
		return true
	}
	switch name {
	case "attributes.type", "_objectType", "_exactDecimals", "_metadata":
		return true
	default:
		return false
	}
}

func mutationField(name string, value any) (semantic.FieldValue, error) {
	field := semantic.FieldValue{Name: name}
	switch value := value.(type) {
	case nil:
		field.Kind, field.Null = semantic.ValueKindNull, true
	case string:
		field.Kind, field.String = semantic.ValueKindString, &value
	case bool:
		field.Kind, field.Boolean = semantic.ValueKindBoolean, &value
	case json.Number:
		exact := string(value)
		field.Kind, field.Number = semantic.ValueKindNumber, &exact
	case map[string]any, []any:
		raw, err := json.Marshal(value)
		if err != nil {
			return semantic.FieldValue{}, err
		}
		canonical, err := semantic.CanonicalSourceJSON(string(raw))
		if err != nil {
			return semantic.FieldValue{}, err
		}
		field.Kind, field.JSON = semantic.ValueKindJSON, &canonical
	default:
		return semantic.FieldValue{}, fmt.Errorf("unsupported JSON value %T", value)
	}
	return field, nil
}

func compositeObjectName(record map[string]any) string {
	if attributes, ok := record["attributes"].(map[string]any); ok {
		if value, ok := attributes["type"].(string); ok {
			return strings.TrimSpace(value)
		}
	}
	if value, ok := record["attributes.type"].(string); ok {
		return strings.TrimSpace(value)
	}
	return ""
}

func compositeRecordID(record map[string]any) (string, error) {
	var id string
	found := false
	for name, value := range record {
		if !strings.EqualFold(name, "Id") {
			continue
		}
		if found {
			return "", errors.New("record identifier is assigned more than once")
		}
		resolved, ok := value.(string)
		if !ok {
			return "", errors.New("record identifier must be a string")
		}
		id, found = strings.TrimSpace(resolved), true
	}
	if !found || id == "" {
		return "", errors.New("record identifier is required")
	}
	return id, nil
}
