package mcp

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

const (
	sessionTTL     = 30 * time.Minute
	cleanupTick    = 5 * time.Minute
)

type session struct {
	id        string
	createdAt time.Time
	lastSeen  time.Time
}

// SessionStore manages MCP session lifecycle with automatic TTL expiry.
type SessionStore struct {
	mu       sync.RWMutex
	sessions map[string]*session
	stop     chan struct{}
}

func NewSessionStore() *SessionStore {
	s := &SessionStore{
		sessions: make(map[string]*session),
		stop:     make(chan struct{}),
	}
	go s.cleanup()
	return s
}

// Create generates a new session and returns its ID.
func (s *SessionStore) Create() string {
	id := generateSessionID()
	s.mu.Lock()
	s.sessions[id] = &session{
		id:        id,
		createdAt: time.Now(),
		lastSeen:  time.Now(),
	}
	s.mu.Unlock()
	return id
}

// Touch updates the last-seen timestamp. Returns false if session not found.
func (s *SessionStore) Touch(id string) bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	sess, ok := s.sessions[id]
	if !ok {
		return false
	}
	sess.lastSeen = time.Now()
	return true
}

// Delete removes a session. Returns false if not found.
func (s *SessionStore) Delete(id string) bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	_, ok := s.sessions[id]
	if ok {
		delete(s.sessions, id)
	}
	return ok
}

// Stop terminates the background cleanup goroutine.
func (s *SessionStore) Stop() {
	close(s.stop)
}

func (s *SessionStore) cleanup() {
	ticker := time.NewTicker(cleanupTick)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			s.mu.Lock()
			cutoff := time.Now().Add(-sessionTTL)
			for id, sess := range s.sessions {
				if sess.lastSeen.Before(cutoff) {
					delete(s.sessions, id)
				}
			}
			s.mu.Unlock()
		case <-s.stop:
			return
		}
	}
}

func generateSessionID() string {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		panic("crypto/rand failed: " + err.Error())
	}
	return hex.EncodeToString(b)
}
