package session

import (
	"context"
	"time"

	"github.com/pkg/errors"
)

// ErrNoActiveSessions is returned when a session group does not contain any
// active session IDs.
var ErrNoActiveSessions = errors.New("no active sessions")

type Group interface {
	SessionIterator() Iterator
}
type Iterator interface {
	NextSession() string
}

func NewGroup(ids ...string) Group {
	return &group{ids: ids}
}

type group struct {
	ids []string
}

func (g *group) SessionIterator() Iterator {
	return &group{ids: g.ids}
}

func (g *group) NextSession() string {
	if len(g.ids) == 0 {
		return ""
	}
	v := g.ids[0]
	g.ids = g.ids[1:]
	return v
}

func AllSessionIDs(g Group) (out []string) {
	if g == nil {
		return nil
	}
	it := g.SessionIterator()
	if it == nil {
		return nil
	}
	for {
		v := it.NextSession()
		if v == "" {
			return
		}
		out = append(out, v)
	}
}

func (sm *Manager) Any(ctx context.Context, g Group, f func(context.Context, string, Caller) error) error {
	if g == nil {
		return nil
	}

	iter := g.SessionIterator()
	if iter == nil {
		return nil
	}

	var lastErr error
	for {
		id := iter.NextSession()
		if id == "" {
			if lastErr != nil {
				return lastErr
			}
			return errors.WithStack(ErrNoActiveSessions)
		}

		timeoutCtx, cancel := context.WithCancelCause(ctx)
		timeoutCtx, _ = context.WithTimeoutCause(timeoutCtx, 5*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet
		defer func() { cancel(errors.WithStack(context.Canceled)) }()
		c, err := sm.Get(timeoutCtx, id, false)
		if err != nil {
			lastErr = err
			continue
		}
		if err := f(c.Context(ctx), id, c); err != nil {
			lastErr = err
			continue
		}
		return nil
	}
}
