package layoutbundleslotv1

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

	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/layoutbundlekeyv1"
)

// CanonicalBehaviorBytes validates behavior and returns its exact RFC 8785 JCS
// bytes. Every v1 value grammar is restricted to JSON-literal-safe ASCII, so
// bytewise key order and direct string emission are the JCS representation.
func CanonicalBehaviorBytes(behavior Behavior) ([]byte, error) {
	if err := validateBehavior(behavior); err != nil {
		return nil, err
	}
	out := make([]byte, 0, 384)
	out = append(out, `{"local_component_key":"`...)
	out = append(out, behavior.LocalComponentKey...)
	out = append(out, '"')
	if behavior.PreviousTargetComponentID != nil {
		out = append(out, `,"previous_target_component_id":"`...)
		out = append(out, (*behavior.PreviousTargetComponentID)...)
		out = append(out, '"')
	}
	out = append(out, `,"schema_version":"`...)
	out = append(out, behavior.SchemaVersion...)
	out = append(out, `","target_component_id":"`...)
	out = append(out, behavior.TargetComponentID...)
	out = append(out, `"}`...)
	return out, nil
}

// ParseCanonicalBehavior parses the closed behavior object and rejects unknown,
// missing, duplicate, null, non-string, malformed-Unicode, or non-JCS input.
func ParseCanonicalBehavior(raw []byte) (Behavior, error) {
	if !utf8.Valid(raw) {
		return Behavior{}, fmt.Errorf("layout-bundle slot behavior must contain valid UTF-8")
	}
	members, err := decodeBehaviorObject(raw)
	if err != nil {
		return Behavior{}, err
	}
	for name := range members {
		switch name {
		case "schema_version", "local_component_key", "target_component_id", "previous_target_component_id":
		default:
			return Behavior{}, fmt.Errorf("layout-bundle slot behavior contains unknown field %q", name)
		}
	}
	for _, name := range []string{"schema_version", "local_component_key", "target_component_id"} {
		if _, exists := members[name]; !exists {
			return Behavior{}, fmt.Errorf("layout-bundle slot behavior required field %q is absent", name)
		}
	}
	schemaVersion, err := decodeBehaviorString("schema_version", members["schema_version"])
	if err != nil {
		return Behavior{}, err
	}
	localComponentKey, err := decodeBehaviorString("local_component_key", members["local_component_key"])
	if err != nil {
		return Behavior{}, err
	}
	targetComponentID, err := decodeBehaviorString("target_component_id", members["target_component_id"])
	if err != nil {
		return Behavior{}, err
	}
	behavior := Behavior{
		SchemaVersion:     schemaVersion,
		LocalComponentKey: layoutbundlekeyv1.LocalComponentKey(localComponentKey),
		TargetComponentID: targetComponentID,
	}
	if rawPrevious, exists := members["previous_target_component_id"]; exists {
		previous, decodeErr := decodeBehaviorString("previous_target_component_id", rawPrevious)
		if decodeErr != nil {
			return Behavior{}, decodeErr
		}
		behavior.PreviousTargetComponentID = &previous
	}
	canonical, err := CanonicalBehaviorBytes(behavior)
	if err != nil {
		return Behavior{}, err
	}
	if !bytes.Equal(raw, canonical) {
		return Behavior{}, fmt.Errorf("layout-bundle slot behavior is not exact RFC 8785 JCS")
	}
	return cloneBehavior(behavior), nil
}

func validateBehavior(behavior Behavior) error {
	if behavior.SchemaVersion != BehaviorSchemaVersion {
		return fmt.Errorf("layout-bundle slot behavior schema_version must equal %q", BehaviorSchemaVersion)
	}
	if err := layoutbundlekeyv1.ValidateLocalComponentKey(behavior.LocalComponentKey); err != nil {
		return fmt.Errorf("layout-bundle slot behavior local_component_key: %w", err)
	}
	if err := appauthority.ValidateDigest(behavior.TargetComponentID); err != nil {
		return fmt.Errorf("layout-bundle slot behavior target_component_id: %w", err)
	}
	if behavior.PreviousTargetComponentID != nil {
		if err := appauthority.ValidateDigest(*behavior.PreviousTargetComponentID); err != nil {
			return fmt.Errorf("layout-bundle slot behavior previous_target_component_id: %w", err)
		}
		if *behavior.PreviousTargetComponentID == behavior.TargetComponentID {
			return fmt.Errorf("layout-bundle slot behavior target_component_id must differ from its predecessor")
		}
	}
	return nil
}

func decodeBehaviorObject(raw []byte) (map[string]json.RawMessage, error) {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	opening, err := decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("layout-bundle slot behavior: %w", err)
	}
	if delimiter, ok := opening.(json.Delim); !ok || delimiter != '{' {
		return nil, fmt.Errorf("layout-bundle slot behavior must be a JSON object")
	}
	members := map[string]json.RawMessage{}
	for decoder.More() {
		nameToken, tokenErr := decoder.Token()
		if tokenErr != nil {
			return nil, fmt.Errorf("layout-bundle slot behavior: %w", tokenErr)
		}
		name, ok := nameToken.(string)
		if !ok {
			return nil, fmt.Errorf("layout-bundle slot behavior member name must be a string")
		}
		if _, duplicate := members[name]; duplicate {
			return nil, fmt.Errorf("layout-bundle slot behavior contains duplicate JSON member %q", name)
		}
		var value json.RawMessage
		if decodeErr := decoder.Decode(&value); decodeErr != nil {
			return nil, fmt.Errorf("layout-bundle slot behavior field %q: %w", name, decodeErr)
		}
		members[name] = append(json.RawMessage(nil), value...)
	}
	closing, err := decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("layout-bundle slot behavior: %w", err)
	}
	if delimiter, ok := closing.(json.Delim); !ok || delimiter != '}' {
		return nil, fmt.Errorf("layout-bundle slot behavior must end with a JSON object")
	}
	if err := ensureBehaviorJSONEOF(decoder); err != nil {
		return nil, err
	}
	return members, nil
}

func decodeBehaviorString(name string, raw json.RawMessage) (string, error) {
	if bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
		return "", fmt.Errorf("layout-bundle slot behavior field %q cannot be null", name)
	}
	var value string
	if err := json.Unmarshal(raw, &value); err != nil {
		return "", fmt.Errorf("layout-bundle slot behavior field %q must be a string: %w", name, err)
	}
	return value, nil
}

func ensureBehaviorJSONEOF(decoder *json.Decoder) error {
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		if err == nil {
			return fmt.Errorf("layout-bundle slot behavior contains trailing JSON")
		}
		return fmt.Errorf("layout-bundle slot behavior trailing JSON: %w", err)
	}
	return nil
}

func cloneBehavior(behavior Behavior) Behavior {
	behavior.PreviousTargetComponentID = cloneStringPointer(behavior.PreviousTargetComponentID)
	return behavior
}
