package auth

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

const authCodeTTL = 10 * time.Minute

// AuthCode holds a pending authorization code and its associated PKCE data.
type AuthCode struct {
	ClientID            string
	RedirectURI         string
	Scope               string
	CodeChallenge       string
	CodeChallengeMethod string
	KeyID               string
	ExpiresAt           time.Time
}

// AuthCodeStore issues single-use authorization codes for the OAuth 2.0
// Authorization Code flow.
type AuthCodeStore struct {
	mu    sync.Mutex
	codes map[string]AuthCode
}

func NewAuthCodeStore() *AuthCodeStore {
	s := &AuthCodeStore{codes: make(map[string]AuthCode)}
	go s.sweepLoop()
	return s
}

// Issue stores the entry and returns the raw authorization code.
func (s *AuthCodeStore) Issue(entry AuthCode) (string, error) {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	raw := hex.EncodeToString(b)
	entry.ExpiresAt = time.Now().Add(authCodeTTL)
	s.mu.Lock()
	s.codes[raw] = entry
	s.mu.Unlock()
	return raw, nil
}

// Consume validates and removes the code, returning the associated entry.
func (s *AuthCodeStore) Consume(code string) (AuthCode, bool) {
	s.mu.Lock()
	defer s.mu.Unlock()
	entry, ok := s.codes[code]
	if !ok || time.Now().After(entry.ExpiresAt) {
		delete(s.codes, code)
		return AuthCode{}, false
	}
	delete(s.codes, code)
	return entry, true
}

func (s *AuthCodeStore) sweepLoop() {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()
	for range ticker.C {
		now := time.Now()
		s.mu.Lock()
		for code, entry := range s.codes {
			if now.After(entry.ExpiresAt) {
				delete(s.codes, code)
			}
		}
		s.mu.Unlock()
	}
}
