package webauthn

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/kami/maven/internal/auth"
)

// PasskeySession implements auth.Session backed by WebAuthn passkey assertion.
// The session starts at Layer2 (passkey is enrolled, this session exists) and
// bumps to Layer3 on successful Assert(), which lasts for assertionTTL before
// decaying back to Layer2.
//
// A nil *PasskeySession is a valid zero: it acts like a session with no
// credentials enrolled (always L2, Assert returns ErrStepUpUnsupported).
// This mirrors the FloorSession behavior when passkey is not configured.
type PasskeySession struct {
	mu           sync.Mutex
	assertedAt   time.Time // zero = not asserted this session
	assertionTTL time.Duration
}

// NewPasskeySession creates a session. The caller chooses the assertion TTL
// (how long a step-up gesture remains valid). 5 minutes is a sensible default.
func NewPasskeySession(assertionTTL time.Duration) *PasskeySession {
	if assertionTTL <= 0 {
		assertionTTL = 5 * time.Minute
	}
	return &PasskeySession{assertionTTL: assertionTTL}
}

// CurrentLayer returns L3 if step-up has been asserted within the TTL,
// otherwise L2 (passkey enrolled, this session proven). A nil receiver
// returns L2 (no way to reach L3 without a session).
func (s *PasskeySession) CurrentLayer(_ context.Context, _ auth.Scope) auth.Layer {
	if s == nil {
		return auth.Layer2
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	if !s.assertedAt.IsZero() && time.Since(s.assertedAt) < s.assertionTTL {
		return auth.Layer3
	}
	return auth.Layer2
}

// IsStepUp returns true if the session was recently asserted (within TTL).
func (s *PasskeySession) IsStepUp() bool {
	if s == nil {
		return false
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	return !s.assertedAt.IsZero() && time.Since(s.assertedAt) < s.assertionTTL
}

// Assert records a successful step-up gesture. The session bumps to L3 for
// the assertion TTL. A nil receiver returns ErrStepUpUnsupported.
func (s *PasskeySession) Assert(_ context.Context, _ auth.Scope) error {
	if s == nil {
		return fmt.Errorf("%w: passkey session not configured", auth.ErrStepUpUnsupported)
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	s.assertedAt = time.Now()
	return nil
}
