package authimpl

import (
	"context"
	"time"

	"github.com/grafana/grafana/pkg/infra/db"
)

func (s *UserAuthTokenService) Run(ctx context.Context) error {
	ticker := time.NewTicker(time.Hour)
	maxInactiveLifetime := s.cfg.LoginMaxInactiveLifetime
	maxLifetime := s.cfg.LoginMaxLifetime

	err := s.serverLockService.LockAndExecute(ctx, "cleanup expired auth tokens", time.Hour*12, func(context.Context) {
		if _, err := s.deleteExpiredTokens(ctx, maxInactiveLifetime, maxLifetime); err != nil {
			s.log.Error("An error occurred while deleting expired tokens", "err", err)
		}
		if err := s.deleteOrphanedExternalSessions(ctx); err != nil {
			s.log.Error("An error occurred while deleting orphaned external sessions", "err", err)
		}
	})
	if err != nil {
		s.log.Error("Failed to lock and execute cleanup of expired auth token", "error", err)
	}

	for {
		select {
		case <-ticker.C:
			err = s.serverLockService.LockAndExecute(ctx, "cleanup expired auth tokens", time.Hour*12, func(context.Context) {
				if _, err := s.deleteExpiredTokens(ctx, maxInactiveLifetime, maxLifetime); err != nil {
					s.log.Error("An error occurred while deleting expired tokens", "err", err)
				}
				if err := s.deleteOrphanedExternalSessions(ctx); err != nil {
					s.log.Error("An error occurred while deleting orphaned external sessions", "err", err)
				}
			})
			if err != nil {
				s.log.Error("Failed to lock and execute cleanup of expired auth token", "error", err)
			}

		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

func (s *UserAuthTokenService) deleteExpiredTokens(ctx context.Context, maxInactiveLifetime, maxLifetime time.Duration) (int64, error) {
	createdBefore := getTime().Add(-maxLifetime)
	rotatedBefore := getTime().Add(-maxInactiveLifetime)

	s.log.Debug("Starting cleanup of expired auth tokens", "createdBefore", createdBefore, "rotatedBefore", rotatedBefore)

	var affected int64
	err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
		sql := `DELETE from user_auth_token WHERE created_at <= ? OR rotated_at <= ?`
		res, err := dbSession.Exec(sql, createdBefore.Unix(), rotatedBefore.Unix())
		if err != nil {
			return err
		}

		affected, err = res.RowsAffected()
		if err != nil {
			s.log.Error("Failed to cleanup expired auth tokens", "error", err)
			return nil
		}

		s.log.Debug("Cleanup of expired auth tokens done", "count", affected)

		return nil
	})

	return affected, err
}

func (s *UserAuthTokenService) deleteOrphanedExternalSessions(ctx context.Context) error {
	s.log.Debug("Starting cleanup of external sessions")

	var affected int64
	err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
		sql := `DELETE FROM user_external_session WHERE NOT EXISTS (SELECT 1 FROM user_auth_token WHERE user_external_session.id = user_auth_token.external_session_id)`

		res, err := dbSession.Exec(sql)
		if err != nil {
			return err
		}

		affected, err = res.RowsAffected()
		if err != nil {
			s.log.Error("Failed to cleanup orphaned external sessions", "error", err)
			return nil
		}

		s.log.Debug("Cleanup of orphaned external sessions done", "count", affected)

		return nil
	})

	return err
}
