package outboundintentv1

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

const maxMappedFieldsV1 = 4096

// DecodeUpsertDesiredStateV1 validates the complete intent and decodes the
// closed v1 payload. The payload must be the canonical encoding emitted by the
// neutral Runtime contract; permissive JSON, unknown fields and duplicate or
// reordered association identities fail closed at the process boundary.
func DecodeUpsertDesiredStateV1(intent Intent) (UpsertDesiredStateV1, error) {
	if err := validateIntent(intent); err != nil {
		return UpsertDesiredStateV1{}, invalid()
	}
	if intent.Operation != OperationUpsert || intent.PayloadFormatVersion != 1 {
		return UpsertDesiredStateV1{}, invalid()
	}
	decoder := json.NewDecoder(bytes.NewReader(intent.CanonicalPayload))
	decoder.DisallowUnknownFields()
	decoder.UseNumber()
	var payload UpsertDesiredStateV1
	if err := decoder.Decode(&payload); err != nil {
		return UpsertDesiredStateV1{}, invalid()
	}
	if err := requireJSONEOF(decoder); err != nil {
		return UpsertDesiredStateV1{}, invalid()
	}
	if err := validateUpsertDesiredStateV1(payload); err != nil {
		return UpsertDesiredStateV1{}, invalid()
	}
	canonical, err := json.Marshal(payload)
	if err != nil || !bytes.Equal(canonical, intent.CanonicalPayload) {
		return UpsertDesiredStateV1{}, invalid()
	}
	return payload, nil
}

func validateUpsertDesiredStateV1(payload UpsertDesiredStateV1) error {
	if payload.Version != 1 {
		return fmt.Errorf("desired-state version must be 1")
	}
	if err := identifier("target object", payload.TargetObject); err != nil {
		return err
	}
	if payload.BodyRoot != "" {
		if err := identifier("body root", payload.BodyRoot); err != nil {
			return err
		}
	}
	if len(payload.MappedFields) == 0 || len(payload.MappedFields) > maxMappedFieldsV1 {
		return fmt.Errorf("mapped field count is invalid")
	}
	for name, raw := range payload.MappedFields {
		if err := identifier("mapped field", name); err != nil || len(raw) == 0 || !json.Valid(raw) {
			return fmt.Errorf("mapped field %q is invalid", name)
		}
		var value any
		decoder := json.NewDecoder(bytes.NewReader(raw))
		decoder.UseNumber()
		if err := decoder.Decode(&value); err != nil || requireJSONEOF(decoder) != nil {
			return fmt.Errorf("mapped field %q value is invalid", name)
		}
		canonical, err := json.Marshal(value)
		if err != nil || !bytes.Equal(canonical, raw) {
			return fmt.Errorf("mapped field %q value is not canonical", name)
		}
	}
	if len(payload.Associations) > MaxBindingsPerIntent {
		return fmt.Errorf("association source count is invalid")
	}
	var previous uint32
	for index, association := range payload.Associations {
		if err := identifier("association source object", association.SourceObject); err != nil {
			return err
		}
		if err := identifier("association source record ID", association.SourceRecordID); err != nil {
			return err
		}
		if index > 0 && association.ContractIndex <= previous {
			return fmt.Errorf("association sources are duplicated or not canonical")
		}
		previous = association.ContractIndex
	}
	return nil
}

func requireJSONEOF(decoder *json.Decoder) error {
	if decoder == nil {
		return fmt.Errorf("JSON decoder is required")
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		if err == nil {
			return fmt.Errorf("unexpected trailing JSON")
		}
		return err
	}
	return nil
}
