package exec

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"strings"
)

func mutationReceiptIdentifiersForAction(domain, action string, input, output map[string]json.RawMessage) (map[string]string, bool) {
	spec, ok := mutationReceiptSelectorSpecForOperation(domain, action, input)
	if !ok {
		return nil, false
	}
	identifiers := make(map[string]string)
	for _, key := range spec.requiredInputScalars {
		value, valid := receiptCanonicalInputScalar(input, key)
		if !valid || !setMutationReceiptIdentifier(identifiers, key, value) {
			return nil, false
		}
	}
	for _, key := range spec.optionalInputScalars {
		if _, present := input[key]; !present {
			continue
		}
		value, valid := receiptCanonicalInputScalar(input, key)
		if !valid || !setMutationReceiptIdentifier(identifiers, key, value) {
			return nil, false
		}
	}
	for _, batch := range spec.inputBatches {
		value, present, valid := receiptCanonicalArray(input, batch.sourceKey, batch.elementKey)
		if !present || !valid || !setMutationReceiptIdentifier(identifiers, batch.identifierKey, value) {
			return nil, false
		}
	}
	for _, key := range []string{"requestId", "idempotencyKey"} {
		if _, present := input[key]; !present {
			continue
		}
		value, valid := receiptCanonicalInputScalar(input, key)
		if !valid || !setMutationReceiptIdentifier(identifiers, key, value) {
			return nil, false
		}
	}

	foundOutput := false
	for _, selector := range spec.outputScalars {
		value, present, valid := receiptOutputScalar(output, selector)
		if !valid {
			return nil, false
		}
		if !present {
			continue
		}
		foundOutput = true
		if !setMutationReceiptIdentifier(identifiers, selector.identifierKey, value) {
			return nil, false
		}
	}
	for _, selector := range spec.outputBatches {
		value, present, valid := receiptCanonicalArray(output, selector.sourceKey, selector.elementKey)
		if !valid {
			return nil, false
		}
		if !present {
			continue
		}
		foundOutput = true
		if !setMutationReceiptIdentifier(identifiers, selector.identifierKey, value) {
			return nil, false
		}
	}
	if spec.requireOutputSelector && !foundOutput {
		return nil, false
	}
	return identifiers, true
}

// mutationReceiptSelectorSpecForOperation resolves the nested Workflow action
// from the already validated raw request before selecting its closed contract.
func mutationReceiptSelectorSpecForOperation(domain, action string, input map[string]json.RawMessage) (mutationReceiptSelectorSpec, bool) {
	if domain != "workflow" {
		return mutationReceiptSelectorSpecFor(domain, action)
	}
	var operation string
	if raw, present := input["action"]; present && json.Unmarshal(raw, &operation) == nil {
		operation = strings.TrimSpace(operation)
	}
	if operation == "" {
		return mutationReceiptSelectorSpec{}, false
	}
	var spec mutationReceiptSelectorSpec
	switch action {
	case "plan":
		spec = workflowPlanReceiptSelectorSpec()
		switch operation {
		case "create":
			spec.requireOutputSelector = true
		case "update", "archive", "publish":
			spec.requiredInputScalars = []string{"planId"}
		case "list", "show", "version", "validate":
			return mutationReceiptSelectorSpec{}, false
		default:
			return mutationReceiptSelectorSpec{}, false
		}
	case "trigger":
		spec = workflowTriggerReceiptSelectorSpec()
		switch operation {
		case "create":
			spec.requireOutputSelector = true
		case "update", "enable", "disable", "archive":
			spec.requiredInputScalars = []string{"triggerId"}
		case "list", "show", "validate":
			return mutationReceiptSelectorSpec{}, false
		default:
			return mutationReceiptSelectorSpec{}, false
		}
	case "run":
		spec = workflowRunReceiptSelectorSpec()
		switch operation {
		case "start":
			spec.requireOutputSelector = true
		case "cancel":
			spec.requiredInputScalars = []string{"runId"}
		case "list", "show", "logs":
			return mutationReceiptSelectorSpec{}, false
		default:
			return mutationReceiptSelectorSpec{}, false
		}
	case "step":
		spec = workflowStepMutationSelectorSpec()
		switch operation {
		case "complete", "fail", "pause", "park":
		default:
			return mutationReceiptSelectorSpec{}, false
		}
	default:
		return mutationReceiptSelectorSpec{}, false
	}
	return addMutationReceiptIdentityFields(spec), true
}

func setMutationReceiptIdentifier(identifiers map[string]string, key, value string) bool {
	if key == "" || value == "" {
		return false
	}
	if previous, present := identifiers[key]; present {
		return previous == value
	}
	identifiers[key] = value
	return true
}

func receiptCanonicalInputScalar(object map[string]json.RawMessage, key string) (string, bool) {
	raw, present := object[key]
	if !present {
		return "", false
	}
	return receiptCanonicalRawScalar(raw, mutationReceiptValueLimit(key))
}

func receiptCanonicalRawScalar(raw json.RawMessage, maxRunes int) (string, bool) {
	var stringValue string
	if json.Unmarshal(raw, &stringValue) == nil {
		if stringValue == "" || strings.TrimSpace(stringValue) != stringValue || !validMutationReceiptText(stringValue, maxRunes) {
			return "", false
		}
		return stringValue, true
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	var number json.Number
	if decoder.Decode(&number) != nil || number.String() == "" || len([]rune(number.String())) > maxRunes {
		return "", false
	}
	return number.String(), true
}

func receiptCanonicalArray(object map[string]json.RawMessage, key, elementKey string) (string, bool, bool) {
	raw, present := object[key]
	if !present {
		return "", false, true
	}
	var values []json.RawMessage
	if json.Unmarshal(raw, &values) != nil || len(values) == 0 {
		return "", true, false
	}
	canonical := make([]string, 0, len(values))
	for _, item := range values {
		if elementKey != "" {
			child := decodeReceiptObject(item)
			if child == nil {
				return "", true, false
			}
			item = child[elementKey]
		}
		value, valid := receiptCanonicalRawScalar(item, maxMutationReceiptBatchRunes)
		if !valid {
			return "", true, false
		}
		canonical = append(canonical, value)
	}
	encoded, err := json.Marshal(canonical)
	if err != nil {
		return "", true, false
	}
	return string(encoded), true, true
}

func receiptOutputScalar(object map[string]json.RawMessage, selector mutationReceiptOutputScalar) (string, bool, bool) {
	for _, container := range append([]string{""}, selector.containers...) {
		child := object
		if container != "" {
			child = receiptObjectField(object, container)
		}
		if child == nil {
			continue
		}
		for _, key := range selector.keys {
			if _, present := child[key]; !present {
				continue
			}
			value, valid := receiptCanonicalRawScalar(child[key], maxMutationReceiptValueRunes)
			return value, true, valid
		}
	}
	return "", false, true
}

func validateMutationReceiptAgainstInput(domain, action string, input map[string]json.RawMessage, receipt *mutationSuccessReceipt) bool {
	if receipt == nil || !receipt.Applied || receipt.Action != domain+" "+action {
		return false
	}
	spec, ok := mutationReceiptSelectorSpecForOperation(domain, action, input)
	if !ok {
		return false
	}
	expected, ok := mutationReceiptIdentifiersForInput(spec, input)
	if !ok {
		return false
	}
	for key, value := range expected {
		if got, present := receipt.Identifiers[key]; present && got != value {
			return false
		}
	}
	allowedOutput := spec.allowedOutputIdentifiers()
	for key, value := range receipt.Identifiers {
		if expectedValue, present := expected[key]; present {
			if value != expectedValue {
				return false
			}
			continue
		}
		if _, present := allowedOutput[key]; !present || !validMutationReceiptIdentifierValue(key, value) {
			return false
		}
	}
	for _, key := range spec.requiredInputScalars {
		if _, present := receipt.Identifiers[key]; !present {
			return false
		}
	}
	for _, batch := range spec.inputBatches {
		if _, present := receipt.Identifiers[batch.identifierKey]; !present {
			return false
		}
	}
	if spec.requireOutputSelector {
		found := false
		for key := range allowedOutput {
			if _, present := receipt.Identifiers[key]; present {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}
	expectedIdempotency := firstReceiptString(input, "idempotencyKey", "requestId")
	if expectedIdempotency != "" && receipt.IdempotencyKey != expectedIdempotency {
		return false
	}
	return true
}

func mutationReceiptIdentifiersForInput(spec mutationReceiptSelectorSpec, input map[string]json.RawMessage) (map[string]string, bool) {
	identifiers := make(map[string]string)
	for _, key := range spec.requiredInputScalars {
		value, valid := receiptCanonicalInputScalar(input, key)
		if !valid || !setMutationReceiptIdentifier(identifiers, key, value) {
			return nil, false
		}
	}
	for _, key := range spec.optionalInputScalars {
		if _, present := input[key]; !present {
			continue
		}
		value, valid := receiptCanonicalInputScalar(input, key)
		if !valid || !setMutationReceiptIdentifier(identifiers, key, value) {
			return nil, false
		}
	}
	for _, batch := range spec.inputBatches {
		value, present, valid := receiptCanonicalArray(input, batch.sourceKey, batch.elementKey)
		if !present || !valid || !setMutationReceiptIdentifier(identifiers, batch.identifierKey, value) {
			return nil, false
		}
	}
	for _, key := range []string{"requestId", "idempotencyKey"} {
		if _, present := input[key]; !present {
			continue
		}
		value, valid := receiptCanonicalInputScalar(input, key)
		if !valid || !setMutationReceiptIdentifier(identifiers, key, value) {
			return nil, false
		}
	}
	return identifiers, true
}

func mutationReceiptJSONHasUniqueKeys(raw json.RawMessage) bool {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	if err := scanMutationReceiptJSONValue(decoder); err != nil {
		return false
	}
	return decoder.Decode(new(struct{})) == io.EOF
}

func scanMutationReceiptJSONValue(decoder *json.Decoder) error {
	token, err := decoder.Token()
	if err != nil {
		return err
	}
	if delimiter, ok := token.(json.Delim); ok {
		switch delimiter {
		case '{':
			seen := make(map[string]struct{})
			for decoder.More() {
				keyToken, err := decoder.Token()
				if err != nil {
					return err
				}
				key, ok := keyToken.(string)
				if !ok {
					return fmt.Errorf("receipt object key is not a string")
				}
				if _, duplicate := seen[key]; duplicate {
					return fmt.Errorf("duplicate receipt object key")
				}
				seen[key] = struct{}{}
				if err := scanMutationReceiptJSONValue(decoder); err != nil {
					return err
				}
			}
			end, err := decoder.Token()
			if err != nil || end != json.Delim('}') {
				return fmt.Errorf("unterminated receipt object")
			}
		case '[':
			for decoder.More() {
				if err := scanMutationReceiptJSONValue(decoder); err != nil {
					return err
				}
			}
			end, err := decoder.Token()
			if err != nil || end != json.Delim(']') {
				return fmt.Errorf("unterminated receipt array")
			}
		case '}', ']':
			return fmt.Errorf("unexpected receipt delimiter")
		}
	}
	return nil
}

func validMutationReceiptIdentifierValue(key, value string) bool {
	if strings.TrimSpace(value) != value || value == "" {
		return false
	}
	if strings.HasPrefix(value, "[") {
		var values []string
		if json.Unmarshal([]byte(value), &values) != nil || len(values) == 0 {
			return false
		}
		encoded, err := json.Marshal(values)
		return err == nil && string(encoded) == value && mutationReceiptValueLimit(key) >= len([]rune(value))
	}
	return validMutationReceiptText(value, mutationReceiptValueLimit(key))
}
