package main

import (
	"crypto/ed25519"
	"crypto/rand"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

var now = time.Date(2026, 10, 13, 12, 0, 0, 0, time.UTC)

type Claims struct{ jwt.RegisteredClaims }

func (c Claims) Validate() error {
	if c.Subject == "" || c.ID == "" || c.IssuedAt == nil || c.NotBefore == nil {
		return errors.New("required application claims")
	}
	if c.ExpiresAt == nil || !c.ExpiresAt.After(c.IssuedAt.Time) || c.ExpiresAt.Sub(c.IssuedAt.Time) > 5*time.Minute {
		return errors.New("invalid application lifetime")
	}
	return nil
}
func validClaims() Claims {
	return Claims{jwt.RegisteredClaims{Issuer: "https://issuer.example", Subject: "user-7", Audience: jwt.ClaimStrings{"orders-api"}, ExpiresAt: jwt.NewNumericDate(now.Add(299 * time.Second)), NotBefore: jwt.NewNumericDate(now.Add(-time.Second)), IssuedAt: jwt.NewNumericDate(now.Add(-time.Second)), ID: "public-test-jti"}}
}

// Public test fixture only: this process creates disposable keys and never persists them.
func sign(c Claims, key ed25519.PrivateKey) string {
	t := jwt.NewWithClaims(jwt.SigningMethodEdDSA, c)
	t.Header["typ"] = "orders-at+jwt"
	t.Header["kid"] = "local-test-key"
	s, err := t.SignedString(key)
	if err != nil {
		panic(err)
	}
	return s
}
func verify(raw string, pub ed25519.PublicKey, revoked map[string]bool) error {
	c := new(Claims)
	t, err := jwt.ParseWithClaims(raw, c, func(t *jwt.Token) (any, error) {
		if t.Header["typ"] != "orders-at+jwt" || t.Header["kid"] != "local-test-key" {
			return nil, errors.New("wrong token profile")
		}
		return pub, nil
	}, jwt.WithValidMethods([]string{"EdDSA"}), jwt.WithIssuer("https://issuer.example"), jwt.WithAudience("orders-api"), jwt.WithExpirationRequired(), jwt.WithIssuedAt(), jwt.WithTimeFunc(func() time.Time { return now }))
	if err != nil {
		return err
	}
	if !t.Valid {
		return errors.New("invalid token")
	}
	if revoked[c.ID] {
		return errors.New("revoked")
	}
	return nil
}

// A single in-memory refresh family. The identifiers are public labels, not bearer secrets.
// A real service needs hashed random tokens, durable atomic storage, expiry and a retry policy.
type Family struct {
	mu      sync.Mutex
	current string
	used    map[string]bool
	revoked bool
}

func (f *Family) Rotate(old, next string) string {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.revoked {
		return "family-revoked"
	}
	if f.used[old] {
		f.revoked = true
		return "reuse-revoked-family"
	}
	if old != f.current || old == "" || next == "" || next == old || f.used[next] {
		return "invalid"
	}
	f.used[old] = true
	f.current = next
	return "rotated"
}
func demo() []string {
	pub, key, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}
	c := validClaims()
	out := []string{fmt.Sprint("valid=", verify(sign(c, key), pub, nil) == nil)}
	wrong := c
	wrong.Audience = jwt.ClaimStrings{"billing-api"}
	out = append(out, fmt.Sprint("wrong-audience-rejected=", verify(sign(wrong, key), pub, nil) != nil))
	expired := c
	expired.ExpiresAt = jwt.NewNumericDate(now)
	out = append(out, fmt.Sprint("expired-rejected=", verify(sign(expired, key), pub, nil) != nil))
	out = append(out, fmt.Sprint("revoked-rejected=", verify(sign(c, key), pub, map[string]bool{c.ID: true}) != nil))
	f := &Family{current: "public-r0", used: map[string]bool{}}
	out = append(out, f.Rotate("public-r0", "public-r1"), f.Rotate("public-r0", "public-r2"), f.Rotate("public-r1", "public-r3"))
	return out
}
func main() {
	for _, line := range demo() {
		fmt.Println(line)
	}
}
