package flowcommandv2

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"strings"
	"unicode"
	"unicode/utf8"

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

type dependencySourceDocument struct {
	ObjectKind string `json:"objectKind"`
	SourceID   string `json:"sourceId"`
}

type payloadDocument struct {
	DefinitionRef     string                   `json:"definitionRef"`
	DefinitionVersion string                   `json:"definitionVersion"`
	CanonicalInputs   json.RawMessage          `json:"canonicalInputs"`
	DependencySource  dependencySourceDocument `json:"dependencySource"`
}

type rawPayloadDocument struct {
	DefinitionRef     string          `json:"definitionRef"`
	DefinitionVersion string          `json:"definitionVersion"`
	CanonicalInputs   json.RawMessage `json:"canonicalInputs"`
	DependencySource  json.RawMessage `json:"dependencySource"`
}

// Encode validates and returns the one canonical JSON representation of a v2
// Runtime Flow command payload.
func Encode(payload Payload) ([]byte, error) {
	if err := validatePayload(payload); err != nil {
		return nil, err
	}
	raw, err := json.Marshal(payloadDocument{
		DefinitionRef:     payload.DefinitionRef,
		DefinitionVersion: payload.DefinitionVersion,
		CanonicalInputs:   append(json.RawMessage(nil), payload.CanonicalInputs...),
		DependencySource: dependencySourceDocument{
			ObjectKind: payload.DependencySource.ObjectKind,
			SourceID:   payload.DependencySource.SourceID,
		},
	})
	if err != nil {
		return nil, invalidPayload()
	}
	canonical, err := mutation.CanonicalSourceJSON(string(raw))
	if err != nil || len(canonical) > MaxPayloadBytes {
		return nil, invalidPayload()
	}
	return []byte(canonical), nil
}

// Decode accepts only the exact canonical v2 payload and rejects unknown,
// duplicate, trailing, oversized, or incomplete source-scope data.
func Decode(raw []byte) (Payload, error) {
	if len(raw) == 0 || len(raw) > MaxPayloadBytes || !utf8.Valid(raw) {
		return Payload{}, invalidPayload()
	}
	canonical, err := mutation.CanonicalSourceJSON(string(raw))
	if err != nil || canonical != string(raw) {
		return Payload{}, invalidPayload()
	}
	if err := validateObjectKeys(raw, map[string]struct{}{
		"canonicalInputs":   {},
		"definitionRef":     {},
		"definitionVersion": {},
		"dependencySource":  {},
	}); err != nil {
		return Payload{}, err
	}
	var doc rawPayloadDocument
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&doc); err != nil {
		return Payload{}, invalidPayload()
	}
	if err := requireEOF(decoder); err != nil {
		return Payload{}, err
	}
	if err := validateObjectKeys(doc.DependencySource, map[string]struct{}{
		"objectKind": {},
		"sourceId":   {},
	}); err != nil {
		return Payload{}, err
	}
	var source dependencySourceDocument
	sourceDecoder := json.NewDecoder(bytes.NewReader(doc.DependencySource))
	sourceDecoder.DisallowUnknownFields()
	if err := sourceDecoder.Decode(&source); err != nil {
		return Payload{}, invalidPayload()
	}
	if err := requireEOF(sourceDecoder); err != nil {
		return Payload{}, err
	}
	payload := Payload{
		DefinitionRef:     doc.DefinitionRef,
		DefinitionVersion: doc.DefinitionVersion,
		CanonicalInputs:   append([]byte(nil), doc.CanonicalInputs...),
		DependencySource: DependencySourceScope{
			ObjectKind: source.ObjectKind,
			SourceID:   source.SourceID,
		},
	}
	if err := validatePayload(payload); err != nil {
		return Payload{}, err
	}
	return payload, nil
}

func validateObjectKeys(raw []byte, allowed map[string]struct{}) error {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	token, err := decoder.Token()
	if err != nil || token != json.Delim('{') {
		return invalidPayload()
	}
	seen := make(map[string]struct{}, len(allowed))
	for decoder.More() {
		token, err := decoder.Token()
		key, ok := token.(string)
		if err != nil || !ok {
			return invalidPayload()
		}
		if _, ok := allowed[key]; !ok {
			return invalidPayload()
		}
		if _, duplicate := seen[key]; duplicate {
			return invalidPayload()
		}
		seen[key] = struct{}{}
		var value json.RawMessage
		if err := decoder.Decode(&value); err != nil {
			return invalidPayload()
		}
	}
	if token, err := decoder.Token(); err != nil || token != json.Delim('}') ||
		len(seen) != len(allowed) {
		return invalidPayload()
	}
	if err := requireEOF(decoder); err != nil {
		return err
	}
	return nil
}

func requireEOF(decoder *json.Decoder) error {
	var trailing interface{}
	if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
		return invalidPayload()
	}
	return nil
}

func validatePayload(payload Payload) error {
	if !validOpaque(payload.DefinitionRef) ||
		!validOpaque(payload.DefinitionVersion) ||
		!validOpaque(payload.DependencySource.ObjectKind) ||
		!validOpaque(payload.DependencySource.SourceID) ||
		len(payload.CanonicalInputs) == 0 ||
		len(payload.CanonicalInputs) > MaxPayloadBytes ||
		!utf8.Valid(payload.CanonicalInputs) {
		return invalidPayload()
	}
	canonicalInputs, err := mutation.CanonicalSourceJSON(
		string(payload.CanonicalInputs),
	)
	if err != nil || canonicalInputs != string(payload.CanonicalInputs) {
		return invalidPayload()
	}
	return nil
}

func validOpaque(value string) bool {
	if value == "" || len(value) > MaxIdentifierBytes ||
		!utf8.ValidString(value) || strings.TrimSpace(value) != value {
		return false
	}
	for _, r := range value {
		if unicode.IsControl(r) {
			return false
		}
	}
	return true
}

func invalidPayload() error {
	return errors.New("runtime Flow command payload is invalid")
}
