package invocation

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/workflow/runlimit"
)

const manualOccurrenceDigestDomain = "loop.workflow.manual-occurrence/v1\x00"

const (
	maxJSONNesting = runlimit.MaxRunPayloadJSONNesting
	maxJSONValues  = runlimit.MaxRunPayloadJSONValues

	// Provenance wraps several independently validated deterministic documents.
	// Its envelope adds depth, while Binding source/input-policy fragments are
	// intentionally repeated beside the complete Binding config for audit
	// readability. Give that aggregate its own structural budget instead of
	// accidentally reusing the budget of one caller input document.
	maxSourceProvenanceJSONNesting = 64
	maxSourceProvenanceJSONValues  = maxJSONValues * 8
)

// DeterministicJSONObject strictly decodes one JSON object, rejects duplicate
// object keys at every depth and trailing data, then emits deterministic JSON.
// It is intentionally schema-agnostic: Plan input type validation happens
// before this persistence boundary.
func DeterministicJSONObject(label string, raw []byte, maxBytes int) ([]byte, error) {
	return deterministicJSONObjectImpl(label, raw, maxBytes, maxJSONNesting, maxJSONValues)
}

// DeterministicJSONObjectWithBudget is the shared persistence validator for JSON
// object envelopes whose structure is intentionally larger than one ordinary
// caller-authored document. Callers should prefer DeterministicJSONObject unless
// the aggregate has its own explicit byte, nesting, and value-count budget.
func DeterministicJSONObjectWithBudget(label string, raw []byte, maxBytes, maxNesting, maxValues int) ([]byte, error) {
	if maxNesting < 0 || maxValues < 1 {
		return nil, fmt.Errorf("canonical JSON structural budget must have non-negative nesting and a positive value limit")
	}
	return deterministicJSONObjectImpl(label, raw, maxBytes, maxNesting, maxValues)
}

// DeterministicSourceProvenanceJSONObject deterministically re-encodes the typed audit envelope
// that contains already-validated input and Binding documents. The byte cap
// remains authoritative, while this aggregate-specific depth/value cap is
// large enough to wrap every legal child document without turning a valid
// boundary input into an internal persistence failure.
func DeterministicSourceProvenanceJSONObject(label string, raw []byte, maxBytes int) ([]byte, error) {
	return deterministicJSONObjectImpl(
		label, raw, maxBytes,
		maxSourceProvenanceJSONNesting, maxSourceProvenanceJSONValues,
	)
}

func deterministicJSONObjectImpl(label string, raw []byte, maxBytes int, maxNesting int, maxValues int) ([]byte, error) {
	label = strings.TrimSpace(label)
	if label == "" {
		label = "value"
	}
	if len(raw) == 0 {
		return nil, fmt.Errorf("%s must be a JSON object", label)
	}
	if maxBytes > 0 && len(raw) > maxBytes {
		return nil, fmt.Errorf("%s exceeds %d bytes", label, maxBytes)
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	values := 0
	value, err := decodeDeterministicValue(decoder, 0, &values, maxNesting, maxValues)
	if err != nil {
		return nil, fmt.Errorf("decode %s: %w", label, err)
	}
	if _, ok := value.(map[string]any); !ok {
		return nil, fmt.Errorf("%s must be a JSON object", label)
	}
	if _, err := decoder.Token(); !errors.Is(err, io.EOF) {
		if err == nil {
			return nil, fmt.Errorf("%s must contain exactly one JSON value", label)
		}
		return nil, fmt.Errorf("decode %s trailing data: %w", label, err)
	}
	payload, err := json.Marshal(value)
	if err != nil {
		return nil, fmt.Errorf("encode canonical %s: %w", label, err)
	}
	if maxBytes > 0 && len(payload) > maxBytes {
		return nil, fmt.Errorf("canonical %s exceeds %d bytes", label, maxBytes)
	}
	return payload, nil
}

// InputDigest returns the self-describing SHA-256 digest of the deterministic final
// Invocation input. Callers must pass the deterministic JSON object emitted by
// DeterministicJSONObject or Plan input normalization.
func InputDigest(deterministicInput []byte) string {
	digest := sha256.Sum256(deterministicInput)
	return "sha256:" + hex.EncodeToString(digest[:])
}

func decodeDeterministicValue(decoder *json.Decoder, depth int, values *int, maxNesting int, maxValues int) (any, error) {
	if depth > maxNesting {
		return nil, fmt.Errorf("JSON nesting exceeds %d", maxNesting)
	}
	*values = *values + 1
	if *values > maxValues {
		return nil, fmt.Errorf("JSON value count exceeds %d", maxValues)
	}
	token, err := decoder.Token()
	if err != nil {
		return nil, err
	}
	delim, isDelim := token.(json.Delim)
	if !isDelim {
		if number, ok := token.(json.Number); ok {
			return normalizedJSONNumber(string(number))
		}
		return token, nil
	}
	switch delim {
	case '{':
		object := map[string]any{}
		for decoder.More() {
			keyToken, err := decoder.Token()
			if err != nil {
				return nil, err
			}
			key, ok := keyToken.(string)
			if !ok {
				return nil, fmt.Errorf("object key must be a string")
			}
			if _, duplicate := object[key]; duplicate {
				return nil, fmt.Errorf("duplicate object key %q", key)
			}
			value, err := decodeDeterministicValue(decoder, depth+1, values, maxNesting, maxValues)
			if err != nil {
				return nil, err
			}
			object[key] = value
		}
		end, err := decoder.Token()
		if err != nil {
			return nil, err
		}
		if end != json.Delim('}') {
			return nil, fmt.Errorf("object is not terminated")
		}
		return object, nil
	case '[':
		array := []any{}
		for decoder.More() {
			value, err := decodeDeterministicValue(decoder, depth+1, values, maxNesting, maxValues)
			if err != nil {
				return nil, err
			}
			array = append(array, value)
		}
		end, err := decoder.Token()
		if err != nil {
			return nil, err
		}
		if end != json.Delim(']') {
			return nil, fmt.Errorf("array is not terminated")
		}
		return array, nil
	default:
		return nil, fmt.Errorf("unexpected delimiter %q", delim)
	}
}

type normalizedNumber string

func (number normalizedNumber) MarshalJSON() ([]byte, error) {
	payload := []byte(number)
	if !json.Valid(payload) {
		return nil, fmt.Errorf("invalid canonical JSON number %q", number)
	}
	return payload, nil
}

// normalizedJSONNumber first applies the runtime's finite float64 number
// semantics, then removes exponent notation, insignificant zeroes and negative
// zero so persisted bytes and their digest remain deterministic.
func normalizedJSONNumber(source string) (normalizedNumber, error) {
	normalizedNum, err := runlimit.NormalizedNumberString(source)
	return normalizedNumber(normalizedNum), err
}

// ManualOccurrenceRef is a stable, non-identifying occurrence key scoped by
// the directly invoked Plan, invoker, and caller idempotency key.
func ManualOccurrenceRef(planID string, actor Actor, idempotencyKey string) string {
	hash := sha256.New()
	_, _ = hash.Write([]byte(manualOccurrenceDigestDomain))
	for _, part := range []string{planID, actor.Type, actor.ID, idempotencyKey} {
		_, _ = hash.Write([]byte{0})
		_, _ = hash.Write([]byte(part))
	}
	return "manual:sha256:" + hex.EncodeToString(hash.Sum(nil))
}

// ScheduleOccurrenceRef is the deterministic self-describing identity for one
// source generation's exact durable due instant. Invocation and Binding source
// timestamps use UTC DATETIME(6).
func ScheduleOccurrenceRef(sourceID string, occurrence time.Time) string {
	return "schedule:" + strings.TrimSpace(sourceID) + ":" + occurrence.UTC().Truncate(time.Microsecond).Format(time.RFC3339Nano)
}
