package auth

import (
	"crypto/rand"
	"encoding/hex"
	"sync"
	"time"
)

const defaultTokenTTL = time.Hour

type tokenEntry struct {
	keyID     string
	expiresAt time.Time
}

// TokenStore issues and validates short-lived opaque access tokens for OAuth
// client credentials flow.
type TokenStore struct {
	mu     sync.RWMutex
	tokens map[string]tokenEntry
	ttl    time.Duration
}

func NewTokenStore(ttl time.Duration) *TokenStore {
	if ttl <= 0 {
		ttl = defaultTokenTTL
	}
	s := &TokenStore{
		tokens: make(map[string]tokenEntry),
		ttl:    ttl,
	}
	go s.sweepLoop()
	return s
}

// Issue generates a new token for the given keyID and returns the token and its TTL.
func (s *TokenStore) Issue(keyID string) (string, time.Duration, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", 0, err
	}
	token := hex.EncodeToString(b)
	s.mu.Lock()
	s.tokens[token] = tokenEntry{keyID: keyID, expiresAt: time.Now().Add(s.ttl)}
	s.mu.Unlock()
	return token, s.ttl, nil
}

// Lookup validates a token and returns the associated keyID.
func (s *TokenStore) Lookup(token string) (string, bool) {
	s.mu.RLock()
	entry, ok := s.tokens[token]
	s.mu.RUnlock()
	if !ok || time.Now().After(entry.expiresAt) {
		return "", false
	}
	return entry.keyID, true
}

func (s *TokenStore) sweepLoop() {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()
	for range ticker.C {
		now := time.Now()
		s.mu.Lock()
		for token, entry := range s.tokens {
			if now.After(entry.expiresAt) {
				delete(s.tokens, token)
			}
		}
		s.mu.Unlock()
	}
}
