package delegationv1

import (
	"crypto/ed25519"
	"encoding/base64"
	"errors"
	"strings"
	"time"
)

// ErrInvalidDelegation is the intentionally generic error text shared by both
// disclosure-safe Failure categories.
var ErrInvalidDelegation = errors.New("runtime delegation: invalid")

// FailureCategory is the only verifier classification exposed to Runtime
// admission. It preserves P6a's public semantics without exposing which key,
// tenant, session, installation, or capability exists.
type FailureCategory uint8

const (
	FailureUnauthenticated FailureCategory = iota + 1
	FailureForbidden
)

// Failure carries one disclosure-safe category. Error intentionally has no
// condition-specific text.
type Failure struct {
	Category FailureCategory
}

func (f *Failure) Error() string { return ErrInvalidDelegation.Error() }
func (f *Failure) Unwrap() error { return ErrInvalidDelegation }

func invalid(category FailureCategory) error { return &Failure{Category: category} }

// KeyState describes which verification keys an immutable verifier accepts.
// Current and Overlap keys verify; Retired keys fail closed. P6-D owns the
// production overlap duration and key-distribution process.
type KeyState uint8

const (
	KeyStateCurrent KeyState = iota + 1
	KeyStateOverlap
	KeyStateRetired
)

// VerificationKey is public verification material only. Production key
// loading, refresh, storage, and retirement remain Runtime Identity concerns.
type VerificationKey struct {
	SigningKeyID string
	PublicKey    ed25519.PublicKey
	State        KeyState
}

// VerifierConfig constructs an immutable, concurrency-safe verifier. The
// supported capability set is supplied by the Runtime build; unknown signed
// capabilities fail closed rather than being ignored.
type VerifierConfig struct {
	Keys                  []VerificationKey
	SupportedCapabilities []string
	ClockSkew             time.Duration
}

// ExpectedBindings are facts known by the accepting transport/admission
// owner. Requiring them prevents a cryptographically valid token from being
// substituted across workload, audience, org, AppInstallation, or RPC family.
type ExpectedBindings struct {
	OrgID              string
	CallerWorkloadID   string
	AppInstallationID  string
	RuntimeAudience    string
	RequiredCapability string
}

// ExpectedCallerBindings are the authority facts available before the signed
// Org and AppInstallation selectors are known. VerifyCallerBound authenticates
// those selectors but does not authorize them; the Runtime admission owner must
// immediately validate the returned claims against its authoritative security
// state before installing trusted request context.
//
// This split is required for multi-installation workloads: one authenticated
// workload may serve more than one Org/AppInstallation, so transport identity
// cannot truthfully preselect a single installation before signature
// verification.
type ExpectedCallerBindings struct {
	CallerWorkloadID   string
	RuntimeAudience    string
	RequiredCapability string
}

// Verifier contains cloned key and capability data and is safe for concurrent
// use. Replace the whole verifier to publish a rotated immutable key snapshot.
type Verifier struct {
	keys                  map[string]VerificationKey
	supportedCapabilities map[string]struct{}
	clockSkew             time.Duration
}

// NewVerifier validates and clones one immutable key/capability snapshot.
func NewVerifier(config VerifierConfig) (*Verifier, error) {
	if config.ClockSkew < 0 || config.ClockSkew > MaxClockSkew || config.ClockSkew%time.Second != 0 {
		return nil, errors.New("runtime delegation: invalid clock skew")
	}
	if len(config.Keys) == 0 || len(config.SupportedCapabilities) == 0 || len(config.SupportedCapabilities) > MaxAuthorityCapabilities {
		return nil, errors.New("runtime delegation: verifier requires keys and supported capabilities")
	}
	verifier := &Verifier{
		keys:                  make(map[string]VerificationKey, len(config.Keys)),
		supportedCapabilities: make(map[string]struct{}, len(config.SupportedCapabilities)),
		clockSkew:             config.ClockSkew,
	}
	currentKeys := 0
	for _, key := range config.Keys {
		if !validKeyID(key.SigningKeyID) || len(key.PublicKey) != ed25519.PublicKeySize ||
			(key.State != KeyStateCurrent && key.State != KeyStateOverlap && key.State != KeyStateRetired) {
			return nil, errors.New("runtime delegation: invalid verification key")
		}
		if _, exists := verifier.keys[key.SigningKeyID]; exists {
			return nil, errors.New("runtime delegation: duplicate verification key")
		}
		clone := append(ed25519.PublicKey(nil), key.PublicKey...)
		verifier.keys[key.SigningKeyID] = VerificationKey{SigningKeyID: key.SigningKeyID, PublicKey: clone, State: key.State}
		if key.State == KeyStateCurrent {
			currentKeys++
		}
	}
	if currentKeys != 1 {
		return nil, errors.New("runtime delegation: verifier requires exactly one current key")
	}
	previous := ""
	for _, capability := range config.SupportedCapabilities {
		if !validCapability(capability) || len(capability) > MaxCapabilityBytes || capability <= previous {
			return nil, errors.New("runtime delegation: supported capabilities must be canonical")
		}
		verifier.supportedCapabilities[capability] = struct{}{}
		previous = capability
	}
	return verifier, nil
}

// Sign creates the only v1 compact JWS form. It owns no key lookup or private
// key storage and accepts only an explicit Ed25519 private key from the issuer.
func Sign(signingKeyID string, privateKey ed25519.PrivateKey, claims Claims) (string, error) {
	if len(privateKey) != ed25519.PrivateKeySize {
		return "", errors.New("runtime delegation: invalid Ed25519 private key")
	}
	input, err := signingInput(signingKeyID, claims)
	if err != nil {
		return "", err
	}
	signature := ed25519.Sign(privateKey, []byte(input))
	compact := input + "." + base64.RawURLEncoding.EncodeToString(signature)
	if err := validateCompactSize(compact); err != nil {
		return "", err
	}
	return compact, nil
}

// Verify authenticates the complete envelope before returning any semantic
// claims. Key selection uses untrusted kid only to select a candidate key; kid,
// version, domain, and claims become trusted only after Ed25519 verification.
func (v *Verifier) Verify(compact string, now time.Time, expected ExpectedBindings) (Claims, error) {
	if validateExpectedBindings(expected) != nil {
		return Claims{}, invalid(FailureUnauthenticated)
	}
	claims, err := v.verifyCallerBound(compact, now, ExpectedCallerBindings{
		CallerWorkloadID: expected.CallerWorkloadID, RuntimeAudience: expected.RuntimeAudience,
		RequiredCapability: expected.RequiredCapability,
	})
	if err != nil {
		return Claims{}, err
	}
	if claims.OrgID != expected.OrgID || claims.AppInstallationID != expected.AppInstallationID {
		return Claims{}, invalid(FailureForbidden)
	}
	return claims, nil
}

// VerifyCallerBound authenticates the complete envelope and binds the
// transport-known workload, Runtime audience, time window, and required RPC
// capability before returning signed claims. It intentionally does not decide
// whether the signed Org/AppInstallation is currently authorized. Callers must
// validate that pair through the authoritative Runtime security-state owner
// before constructing trusted context.
func (v *Verifier) VerifyCallerBound(compact string, now time.Time, expected ExpectedCallerBindings) (Claims, error) {
	if validateExpectedCallerBindings(expected) != nil {
		return Claims{}, invalid(FailureUnauthenticated)
	}
	return v.verifyCallerBound(compact, now, expected)
}

func (v *Verifier) verifyCallerBound(compact string, now time.Time, expected ExpectedCallerBindings) (Claims, error) {
	if v == nil || validateCompactSize(compact) != nil {
		return Claims{}, invalid(FailureUnauthenticated)
	}
	parts := strings.Split(compact, ".")
	if len(parts) != 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" {
		return Claims{}, invalid(FailureUnauthenticated)
	}
	header, err := decodeCanonicalBase64URL(parts[0])
	if err != nil {
		return Claims{}, invalid(FailureUnauthenticated)
	}
	keyID, err := parseCanonicalProtectedHeader(header)
	if err != nil {
		return Claims{}, invalid(FailureUnauthenticated)
	}
	key, exists := v.keys[keyID]
	if !exists || key.State == KeyStateRetired {
		return Claims{}, invalid(FailureUnauthenticated)
	}
	signature, err := decodeCanonicalBase64URL(parts[2])
	if err != nil || len(signature) != ed25519.SignatureSize ||
		!ed25519.Verify(key.PublicKey, []byte(parts[0]+"."+parts[1]), signature) {
		return Claims{}, invalid(FailureUnauthenticated)
	}
	payload, err := decodeCanonicalBase64URL(parts[1])
	if err != nil {
		return Claims{}, invalid(FailureUnauthenticated)
	}
	claims, err := parseCanonicalClaims(payload)
	if err != nil {
		return Claims{}, invalid(FailureUnauthenticated)
	}
	if category := v.validateAuthenticatedCaller(claims, now, expected); category != 0 {
		return Claims{}, invalid(category)
	}
	return claims, nil
}

func (v *Verifier) validateAuthenticatedCaller(claims Claims, now time.Time, expected ExpectedCallerBindings) FailureCategory {
	nowUnix := now.Unix()
	skewSeconds := int64(v.clockSkew / time.Second)
	// Compare integer seconds without adding near the int64 ceiling. Claims are
	// positive and skew is at most 30, so IssuedAt-skew cannot underflow. When
	// now >= ExpiresAt, now-ExpiresAt is non-negative and cannot overflow.
	notYetValid := nowUnix < claims.IssuedAt-skewSeconds
	expired := nowUnix >= claims.ExpiresAt && nowUnix-claims.ExpiresAt >= skewSeconds
	if notYetValid || expired {
		return FailureUnauthenticated
	}
	if claims.RuntimeAudience != expected.RuntimeAudience {
		return FailureUnauthenticated
	}
	if claims.CallerWorkloadID != expected.CallerWorkloadID {
		return FailureForbidden
	}
	foundRequired := false
	for _, capability := range claims.Capabilities {
		if _, supported := v.supportedCapabilities[capability]; !supported {
			return FailureForbidden
		}
		if capability == expected.RequiredCapability {
			foundRequired = true
		}
	}
	if !foundRequired {
		return FailureForbidden
	}
	return 0
}

func validateExpectedCallerBindings(expected ExpectedCallerBindings) error {
	if !validOpaque(expected.CallerWorkloadID, MaxWorkloadIdentifierBytes) ||
		!validOpaque(expected.RuntimeAudience, MaxIdentifierBytes) ||
		!validCapability(expected.RequiredCapability) || len(expected.RequiredCapability) > MaxCapabilityBytes {
		return ErrInvalidDelegation
	}
	return nil
}

func validateExpectedBindings(expected ExpectedBindings) error {
	if !validOpaque(expected.OrgID, MaxIdentifierBytes) ||
		!validOpaque(expected.AppInstallationID, MaxIdentifierBytes) ||
		validateExpectedCallerBindings(ExpectedCallerBindings{
			CallerWorkloadID: expected.CallerWorkloadID, RuntimeAudience: expected.RuntimeAudience,
			RequiredCapability: expected.RequiredCapability,
		}) != nil {
		return ErrInvalidDelegation
	}
	return nil
}

func validateCompactSize(compact string) error {
	if len(compact) == 0 || len(compact) > MaxCompactTokenBytes {
		return ErrInvalidDelegation
	}
	return nil
}

func decodeCanonicalBase64URL(value string) ([]byte, error) {
	decoded, err := base64.RawURLEncoding.DecodeString(value)
	if err != nil || base64.RawURLEncoding.EncodeToString(decoded) != value {
		return nil, ErrInvalidDelegation
	}
	return decoded, nil
}
