package exec

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

func validateWorkflowActionJSON(raw []byte) error {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	first, err := decoder.Token()
	if err != nil {
		return err
	}
	if delimiter, ok := first.(json.Delim); !ok || delimiter != '{' {
		return fmt.Errorf("arguments must be a JSON object")
	}
	if err := consumeWorkflowActionJSON(decoder, first, 0); err != nil {
		return err
	}
	if _, err := decoder.Token(); !errors.Is(err, io.EOF) {
		if err == nil {
			return fmt.Errorf("multiple JSON values")
		}
		return err
	}
	return nil
}

// ValidateWorkflowActionJSON validates the model-authored Workflow action at
// the CLI boundary before the daemon authorization round-trip. It rejects
// duplicate keys at every depth and caps recursion in addition to requiring a
// single root object. The handler repeats the check as a defense-in-depth
// boundary for direct daemon callers.
func ValidateWorkflowActionJSON(raw []byte) error {
	return validateWorkflowActionJSON(raw)
}

func consumeWorkflowActionJSON(decoder *json.Decoder, token json.Token, depth int) error {
	delimiter, composite := token.(json.Delim)
	if !composite {
		return nil
	}
	if depth >= workflowActionMaxJSONNesting {
		return fmt.Errorf("JSON nesting exceeds limit")
	}
	switch delimiter {
	case '{':
		seen := map[string]struct{}{}
		for decoder.More() {
			keyToken, err := decoder.Token()
			if err != nil {
				return err
			}
			key, ok := keyToken.(string)
			if !ok {
				return fmt.Errorf("JSON object key must be a string")
			}
			if _, duplicate := seen[key]; duplicate {
				return fmt.Errorf("duplicate JSON field %q", key)
			}
			seen[key] = struct{}{}
			value, err := decoder.Token()
			if err != nil {
				return err
			}
			if err := consumeWorkflowActionJSON(decoder, value, depth+1); err != nil {
				return err
			}
		}
		closing, err := decoder.Token()
		if err != nil || closing != json.Delim('}') {
			return fmt.Errorf("JSON object is not closed")
		}
	case '[':
		for decoder.More() {
			value, err := decoder.Token()
			if err != nil {
				return err
			}
			if err := consumeWorkflowActionJSON(decoder, value, depth+1); err != nil {
				return err
			}
		}
		closing, err := decoder.Token()
		if err != nil || closing != json.Delim(']') {
			return fmt.Errorf("JSON array is not closed")
		}
	default:
		return fmt.Errorf("unexpected JSON delimiter %q", delimiter)
	}
	return nil
}
