package flowcommandv3

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

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

type executionRefDocument struct {
	ExecutionID        string `json:"executionId"`
	PackGenerationID   string `json:"packGenerationId"`
	ServingEpochDigest string `json:"servingEpochDigest"`
	DefinitionRef      string `json:"definitionRef"`
	DefinitionVersion  string `json:"definitionVersion"`
	InputDigest        string `json:"inputDigest"`
}

type payloadDocument struct {
	CanonicalInputs     json.RawMessage      `json:"canonicalInputs"`
	ExecutionRef        executionRefDocument `json:"executionRef"`
	ExternalEffectsMode ExternalEffectsMode  `json:"externalEffectsMode"`
}

type rawPayloadDocument struct {
	CanonicalInputs     json.RawMessage     `json:"canonicalInputs"`
	ExecutionRef        json.RawMessage     `json:"executionRef"`
	ExternalEffectsMode ExternalEffectsMode `json:"externalEffectsMode"`
}

// Encode validates and returns the one canonical JSON representation of a v3
// Runtime Flow command payload.
func Encode(payload Payload) ([]byte, error) {
	if err := validatePayload(payload); err != nil {
		return nil, err
	}
	ref := payload.ExecutionRef
	raw, err := json.Marshal(payloadDocument{
		CanonicalInputs: append(json.RawMessage(nil), payload.CanonicalInputs...),
		ExecutionRef: executionRefDocument{
			ExecutionID: ref.ExecutionID, PackGenerationID: ref.PackGenerationID,
			ServingEpochDigest: ref.ServingEpochDigest, DefinitionRef: ref.DefinitionRef,
			DefinitionVersion: ref.DefinitionVersion, InputDigest: ref.InputDigest,
		},
		ExternalEffectsMode: payload.ExternalEffectsMode,
	})
	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 v3 payload and rejects unknown,
// duplicate, missing, trailing, oversized, or internally inconsistent facts.
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": {}, "executionRef": {}, "externalEffectsMode": {},
	}); 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.ExecutionRef, map[string]struct{}{
		"executionId": {}, "packGenerationId": {}, "servingEpochDigest": {},
		"definitionRef": {}, "definitionVersion": {}, "inputDigest": {},
	}); err != nil {
		return Payload{}, err
	}

	var refDoc executionRefDocument
	refDecoder := json.NewDecoder(bytes.NewReader(doc.ExecutionRef))
	refDecoder.DisallowUnknownFields()
	if err := refDecoder.Decode(&refDoc); err != nil {
		return Payload{}, invalidPayload()
	}
	if err := requireEOF(refDecoder); err != nil {
		return Payload{}, err
	}

	payload := Payload{
		ExecutionRef: applicationresult.ExecutionRef{
			ExecutionID: refDoc.ExecutionID, PackGenerationID: refDoc.PackGenerationID,
			ServingEpochDigest: refDoc.ServingEpochDigest, DefinitionRef: refDoc.DefinitionRef,
			DefinitionVersion: refDoc.DefinitionVersion, InputDigest: refDoc.InputDigest,
		},
		CanonicalInputs:     append([]byte(nil), doc.CanonicalInputs...),
		ExternalEffectsMode: doc.ExternalEffectsMode,
	}
	if err := validatePayload(payload); err != nil {
		return Payload{}, err
	}
	return payload, nil
}

func validatePayload(payload Payload) error {
	if applicationresult.ValidateExecutionRef(payload.ExecutionRef) != nil ||
		!payload.ExternalEffectsMode.Valid() || !validCanonicalInputs(payload.CanonicalInputs) {
		return invalidPayload()
	}
	digest, err := ComputeInputDigest(payload.CanonicalInputs, payload.ExternalEffectsMode)
	if err != nil || digest != payload.ExecutionRef.InputDigest {
		return invalidPayload()
	}
	return nil
}

func validCanonicalInputs(value []byte) bool {
	if len(value) == 0 || len(value) > MaxPayloadBytes || !utf8.Valid(value) {
		return false
	}
	canonical, err := mutation.CanonicalSourceJSON(string(value))
	return err == nil && canonical == string(value)
}

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()
	}
	return requireEOF(decoder)
}

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

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