package store

import (
	"context"
	"errors"
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"

	"bountyboard/internal/domain"
)

func (s *Store) sessions() *mongo.Collection { return s.DB.Collection("sessions") }

func (s *Store) CreateSession(ctx context.Context, sess *domain.Session) error {
	if _, err := s.sessions().InsertOne(ctx, sess); err != nil {
		return fmt.Errorf("insert session: %w", err)
	}
	return nil
}

// SessionByToken returns a live session. Expired-but-not-yet-reaped rows
// (Mongo TTL sweeps roughly once a minute) are treated as absent.
func (s *Store) SessionByToken(ctx context.Context, token string) (*domain.Session, error) {
	var sess domain.Session
	err := s.sessions().FindOne(ctx, bson.M{
		"_id":       token,
		"expiresAt": bson.M{"$gt": time.Now().UTC()},
	}).Decode(&sess)
	if errors.Is(err, mongo.ErrNoDocuments) {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("find session: %w", err)
	}
	return &sess, nil
}

// RefreshSession slides the expiry window (§7: 30-day sliding expiry).
func (s *Store) RefreshSession(ctx context.Context, token string) error {
	now := time.Now().UTC()
	_, err := s.sessions().UpdateOne(ctx, bson.M{"_id": token}, bson.M{
		"$set": bson.M{"expiresAt": now.Add(domain.SessionTTL), "refreshedAt": now},
	})
	if err != nil {
		return fmt.Errorf("refresh session: %w", err)
	}
	return nil
}

func (s *Store) DeleteSession(ctx context.Context, token string) error {
	if _, err := s.sessions().DeleteOne(ctx, bson.M{"_id": token}); err != nil {
		return fmt.Errorf("delete session: %w", err)
	}
	return nil
}

// DeleteUserSessions revokes every session of a user (logout-all, disable,
// password change).
func (s *Store) DeleteUserSessions(ctx context.Context, userID string) (int64, error) {
	res, err := s.sessions().DeleteMany(ctx, bson.M{"userId": userID})
	if err != nil {
		return 0, fmt.Errorf("delete user sessions: %w", err)
	}
	return res.DeletedCount, nil
}

// DeleteUserSessionsExcept revokes all sessions of a user except one (used
// after a password change to keep the current session alive).
func (s *Store) DeleteUserSessionsExcept(ctx context.Context, userID, keepToken string) (int64, error) {
	res, err := s.sessions().DeleteMany(ctx, bson.M{
		"userId": userID,
		"_id":    bson.M{"$ne": keepToken},
	})
	if err != nil {
		return 0, fmt.Errorf("delete user sessions: %w", err)
	}
	return res.DeletedCount, nil
}
