package delegationv1

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

const (
	// EnvelopeVersion is authenticated in the protected header. An incompatible
	// grammar requires another version rather than a permissive v1 parser.
	EnvelopeVersion = 1
	// JWSAlgorithm is the RFC 8037 algorithm identifier for Ed25519 signatures.
	JWSAlgorithm = "EdDSA"
	// JWSType distinguishes this closed delegation grammar from public user JWTs.
	JWSType = "nexuscrm-runtime-delegation+jwt"
	// SignatureDomain is an authenticated protected-header value. Together with
	// JWSType and EnvelopeVersion it gives this token kind mutually exclusive
	// validation rules and prevents cross-protocol signature reuse.
	SignatureDomain = "nexuscrm.runtime.delegation.v1"
)

type protectedHeader struct {
	Algorithm string `json:"alg"`
	KeyID     string `json:"kid"`
	Type      string `json:"typ"`
	Version   int    `json:"nexuscrm_version"`
	Domain    string `json:"nexuscrm_domain"`
}

// canonicalProtectedHeader returns the only protected-header JSON emitted by
// the v1 issuer. Unknown members, alternate algorithms, and non-canonical JSON
// are rejected by parseCanonicalProtectedHeader.
func canonicalProtectedHeader(keyID string) ([]byte, error) {
	if !validKeyID(keyID) {
		return nil, errors.New("runtime delegation: invalid SigningKeyID")
	}
	return canonicalJSON(protectedHeader{
		Algorithm: JWSAlgorithm,
		KeyID:     keyID,
		Type:      JWSType,
		Version:   EnvelopeVersion,
		Domain:    SignatureDomain,
	})
}

// parseCanonicalProtectedHeader validates the closed v1 header grammar and
// returns its key-selection identifier. It does not locate or authenticate a
// key and must not be treated as signature verification.
func parseCanonicalProtectedHeader(data []byte) (string, error) {
	var header protectedHeader
	if err := decodeClosedJSON(data, &header); err != nil {
		return "", errors.New("runtime delegation: invalid protected header")
	}
	canonical, err := canonicalProtectedHeader(header.KeyID)
	if err != nil || header.Algorithm != JWSAlgorithm || header.Type != JWSType ||
		header.Version != EnvelopeVersion || header.Domain != SignatureDomain || !bytes.Equal(data, canonical) {
		return "", errors.New("runtime delegation: non-canonical protected header")
	}
	return header.KeyID, nil
}

// canonicalClaims returns the deterministic JSON payload signed by Runtime
// Identity. The Claims field order is part of the immutable v1 compatibility
// vector. This function does not sign or authorize the claims.
func canonicalClaims(claims Claims) ([]byte, error) {
	if err := claims.Validate(); err != nil {
		return nil, err
	}
	return canonicalJSON(claims)
}

// parseCanonicalClaims rejects unknown fields, duplicate/non-canonical JSON,
// and invalid semantic shape. It performs no cryptographic or authoritative
// validation; a caller may use the result only after verifying the JWS.
func parseCanonicalClaims(data []byte) (Claims, error) {
	var claims Claims
	if err := decodeClosedJSON(data, &claims); err != nil {
		return Claims{}, errors.New("runtime delegation: invalid claims payload")
	}
	canonical, err := canonicalClaims(claims)
	if err != nil || !bytes.Equal(data, canonical) {
		return Claims{}, errors.New("runtime delegation: non-canonical claims payload")
	}
	return claims, nil
}

// signingInput returns the RFC 7515 compact-serialization signing input:
// BASE64URL(protected header) + "." + BASE64URL(payload), without padding.
// Sign and Verifier are the only public paths that consume this representation.
func signingInput(keyID string, claims Claims) (string, error) {
	header, err := canonicalProtectedHeader(keyID)
	if err != nil {
		return "", err
	}
	payload, err := canonicalClaims(claims)
	if err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(header) + "." + base64.RawURLEncoding.EncodeToString(payload), nil
}

func decodeClosedJSON(data []byte, target any) error {
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(target); err != nil {
		return err
	}
	if err := decoder.Decode(&struct{}{}); err != io.EOF {
		return errors.New("trailing JSON value")
	}
	return nil
}

func canonicalJSON(value any) ([]byte, error) {
	var buffer bytes.Buffer
	encoder := json.NewEncoder(&buffer)
	// HTML escaping is not part of the v1 grammar. Disabling it makes the byte
	// bound depend on UTF-8 input bytes rather than context-specific escaping.
	encoder.SetEscapeHTML(false)
	if err := encoder.Encode(value); err != nil {
		return nil, err
	}
	return bytes.TrimSuffix(buffer.Bytes(), []byte{'\n'}), nil
}

func validKeyID(value string) bool {
	if value == "" || len(value) > MaxSigningKeyIDBytes {
		return false
	}
	for i := 0; i < len(value); i++ {
		ch := value[i]
		if !lowerAlpha(ch) && !asciiDigit(ch) && !(ch >= 'A' && ch <= 'Z') && ch != '.' && ch != '_' && ch != '-' {
			return false
		}
	}
	return true
}
