package persistence

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

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

const runtimePlatformCommandApplicationCompletionVersion uint32 = 3

var errRuntimePlatformCommandApplicationCompletionInvalid = errors.New(
	"runtime platform command application completion is invalid",
)

// RuntimePlatformCommandApplicationCompletion is the Runtime-owned terminal
// application disposition and its typed result body. The private durable codec
// below is the only representation written into a v3 command's ResultPayload.
type RuntimePlatformCommandApplicationCompletion struct {
	Outcome     applicationresultv1.Outcome
	FailureCode applicationresultv1.FailureCode
	Body        json.RawMessage
}

type runtimePlatformCommandApplicationCompletionEnvelope struct {
	Version     uint32                          `json:"version"`
	Outcome     applicationresultv1.Outcome     `json:"outcome"`
	FailureCode applicationresultv1.FailureCode `json:"failureCode"`
	Body        json.RawMessage                 `json:"body"`
}

type runtimePlatformCommandApplicationCompletionDecodeEnvelope struct {
	Version     *uint32                          `json:"version"`
	Outcome     *applicationresultv1.Outcome     `json:"outcome"`
	FailureCode *applicationresultv1.FailureCode `json:"failureCode"`
	Body        json.RawMessage                  `json:"body"`
}

func encodeRuntimePlatformCommandApplicationCompletion(
	completion RuntimePlatformCommandApplicationCompletion,
) ([]byte, error) {
	if !validRuntimePlatformCommandApplicationCompletion(completion) {
		return nil, errRuntimePlatformCommandApplicationCompletionInvalid
	}
	raw, err := json.Marshal(runtimePlatformCommandApplicationCompletionEnvelope{
		Version:     runtimePlatformCommandApplicationCompletionVersion,
		Outcome:     completion.Outcome,
		FailureCode: completion.FailureCode,
		Body:        completion.Body,
	})
	if err != nil {
		return nil, errRuntimePlatformCommandApplicationCompletionInvalid
	}
	canonical, err := recordmutationv1.CanonicalSourceJSON(string(raw))
	if err != nil || len(canonical) > applicationresultv1.MaxResultPayloadBytes {
		return nil, errRuntimePlatformCommandApplicationCompletionInvalid
	}
	return []byte(canonical), nil
}

func decodeRuntimePlatformCommandApplicationCompletion(
	raw []byte,
) (RuntimePlatformCommandApplicationCompletion, error) {
	if len(raw) == 0 || len(raw) > applicationresultv1.MaxResultPayloadBytes ||
		validateRuntimePlatformCommandApplicationCompletionEnvelopeKeys(raw) != nil {
		return RuntimePlatformCommandApplicationCompletion{},
			errRuntimePlatformCommandApplicationCompletionInvalid
	}
	canonical, err := recordmutationv1.CanonicalSourceJSON(string(raw))
	if err != nil || canonical != string(raw) {
		return RuntimePlatformCommandApplicationCompletion{},
			errRuntimePlatformCommandApplicationCompletionInvalid
	}

	var envelope runtimePlatformCommandApplicationCompletionDecodeEnvelope
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&envelope); err != nil || requireRuntimePlatformCommandApplicationCompletionEOF(decoder) != nil ||
		envelope.Version == nil || *envelope.Version != runtimePlatformCommandApplicationCompletionVersion ||
		envelope.Outcome == nil || envelope.FailureCode == nil {
		return RuntimePlatformCommandApplicationCompletion{},
			errRuntimePlatformCommandApplicationCompletionInvalid
	}
	completion := RuntimePlatformCommandApplicationCompletion{
		Outcome:     *envelope.Outcome,
		FailureCode: *envelope.FailureCode,
		Body:        append(json.RawMessage(nil), envelope.Body...),
	}
	if !validRuntimePlatformCommandApplicationCompletion(completion) {
		return RuntimePlatformCommandApplicationCompletion{},
			errRuntimePlatformCommandApplicationCompletionInvalid
	}
	return completion, nil
}

func validRuntimePlatformCommandApplicationCompletion(
	completion RuntimePlatformCommandApplicationCompletion,
) bool {
	if !completion.Outcome.Valid() || !completion.FailureCode.ValidFor(completion.Outcome) ||
		len(completion.Body) == 0 {
		return false
	}
	canonical, err := recordmutationv1.CanonicalSourceJSON(string(completion.Body))
	return err == nil && canonical == string(completion.Body)
}

func validateRuntimePlatformCommandApplicationCompletionEnvelopeKeys(raw []byte) error {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	start, err := decoder.Token()
	if err != nil || start != json.Delim('{') {
		return errRuntimePlatformCommandApplicationCompletionInvalid
	}
	allowed := map[string]struct{}{
		"version":     {},
		"outcome":     {},
		"failureCode": {},
		"body":        {},
	}
	seen := make(map[string]struct{}, len(allowed))
	for decoder.More() {
		token, err := decoder.Token()
		key, ok := token.(string)
		if err != nil || !ok {
			return errRuntimePlatformCommandApplicationCompletionInvalid
		}
		if _, ok := allowed[key]; !ok {
			return errRuntimePlatformCommandApplicationCompletionInvalid
		}
		if _, duplicate := seen[key]; duplicate {
			return errRuntimePlatformCommandApplicationCompletionInvalid
		}
		seen[key] = struct{}{}
		var value json.RawMessage
		if err := decoder.Decode(&value); err != nil {
			return errRuntimePlatformCommandApplicationCompletionInvalid
		}
	}
	end, err := decoder.Token()
	if err != nil || end != json.Delim('}') || len(seen) != len(allowed) ||
		requireRuntimePlatformCommandApplicationCompletionEOF(decoder) != nil {
		return errRuntimePlatformCommandApplicationCompletionInvalid
	}
	return nil
}

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