package appidentitykeydeclarationv1

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

// CanonicalBehaviorBytes validates behavior and returns its exact RFC 8785 JCS
// bytes. The v1 schema value is ASCII and needs no JSON escaping.
func CanonicalBehaviorBytes(behavior Behavior) ([]byte, error) {
	if err := validateBehavior(behavior); err != nil {
		return nil, err
	}
	return []byte(`{"schema_version":"` + behavior.SchemaVersion + `"}`), 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("App identity-key declaration behavior must contain valid UTF-8")
	}
	members, err := decodeBehaviorObject(raw)
	if err != nil {
		return Behavior{}, err
	}
	for name := range members {
		if name != "schema_version" {
			return Behavior{}, fmt.Errorf("App identity-key declaration behavior contains unknown field %q", name)
		}
	}
	rawSchemaVersion, exists := members["schema_version"]
	if !exists {
		return Behavior{}, fmt.Errorf("App identity-key declaration behavior required field %q is absent", "schema_version")
	}
	schemaVersion, err := decodeBehaviorString("schema_version", rawSchemaVersion)
	if err != nil {
		return Behavior{}, err
	}
	behavior := Behavior{SchemaVersion: schemaVersion}
	canonical, err := CanonicalBehaviorBytes(behavior)
	if err != nil {
		return Behavior{}, err
	}
	if !bytes.Equal(raw, canonical) {
		return Behavior{}, fmt.Errorf("App identity-key declaration behavior is not exact RFC 8785 JCS")
	}
	return behavior, nil
}

func validateBehavior(behavior Behavior) error {
	if behavior.SchemaVersion != BehaviorSchemaVersion {
		return fmt.Errorf("App identity-key declaration behavior schema_version must equal %q", BehaviorSchemaVersion)
	}
	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("App identity-key declaration behavior: %w", err)
	}
	if delimiter, ok := opening.(json.Delim); !ok || delimiter != '{' {
		return nil, fmt.Errorf("App identity-key declaration 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("App identity-key declaration behavior: %w", tokenErr)
		}
		name, ok := nameToken.(string)
		if !ok {
			return nil, fmt.Errorf("App identity-key declaration behavior member name must be a string")
		}
		if _, duplicate := members[name]; duplicate {
			return nil, fmt.Errorf("App identity-key declaration behavior contains duplicate JSON member %q", name)
		}
		var value json.RawMessage
		if decodeErr := decoder.Decode(&value); decodeErr != nil {
			return nil, fmt.Errorf("App identity-key declaration behavior field %q: %w", name, decodeErr)
		}
		members[name] = append(json.RawMessage(nil), value...)
	}
	closing, err := decoder.Token()
	if err != nil {
		return nil, fmt.Errorf("App identity-key declaration behavior: %w", err)
	}
	if delimiter, ok := closing.(json.Delim); !ok || delimiter != '}' {
		return nil, fmt.Errorf("App identity-key declaration 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("App identity-key declaration behavior field %q cannot be null", name)
	}
	var value string
	if err := json.Unmarshal(raw, &value); err != nil {
		return "", fmt.Errorf("App identity-key declaration 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("App identity-key declaration behavior contains trailing JSON")
		}
		return fmt.Errorf("App identity-key declaration behavior trailing JSON: %w", err)
	}
	return nil
}
