package repositories

import (
	"astrology-api/constants"
	models "astrology-api/models/usermodel"
	"errors"

	"gorm.io/gorm"
)

// The reads and writes behind the waiting queue (astrologer_waiting_queue).
//
// Every transition runs inside a transaction that first locks the
// astrologer's row (LockAstrologerQueue), so two customers joining, a sweep
// and a session ending cannot reorder or double-notify one astrologer's queue.

func (r *consultationRepository) dbOr(tx *gorm.DB) *gorm.DB {

	if tx != nil {
		return tx
	}

	return r.db
}

func (r *consultationRepository) LockAstrologerQueue(tx *gorm.DB, astrologerID uint) error {

	var id uint

	return tx.
		Raw("SELECT id FROM astrologers WHERE id = ? FOR UPDATE", astrologerID).
		Scan(&id).Error
}

// ListQueueForAstrologer returns the rows still in play for one astrologer —
// the active ones plus CONNECTED, whose session may have ended — oldest first,
// which is the FIFO order.
func (r *consultationRepository) ListQueueForAstrologer(
	tx *gorm.DB,
	astrologerID uint,
) ([]models.AstrologerWaitingQueue, error) {

	var rows []models.AstrologerWaitingQueue

	statuses := append(constants.ActiveQueueStatuses(), constants.QueueStatusConnected)

	err := r.dbOr(tx).
		Where("astrologer_id = ?", astrologerID).
		Where("status IN ?", statuses).
		Order("requested_at ASC, id ASC").
		Find(&rows).Error

	return rows, err
}

func (r *consultationRepository) FindActiveQueueEntry(
	tx *gorm.DB,
	userID uint,
	astrologerID uint,
	waitingType string,
) (*models.AstrologerWaitingQueue, error) {

	var row models.AstrologerWaitingQueue

	err := r.dbOr(tx).
		Where("user_id = ?", userID).
		Where("astrologer_id = ?", astrologerID).
		Where("waiting_type = ?", waitingType).
		Where("status IN ?", constants.ActiveQueueStatuses()).
		Order("id DESC").
		First(&row).Error

	if err != nil {

		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}

		return nil, err
	}

	return &row, nil
}

func (r *consultationRepository) CreateQueueEntry(tx *gorm.DB, row *models.AstrologerWaitingQueue) error {
	return r.dbOr(tx).Create(row).Error
}

func (r *consultationRepository) GetQueueEntry(id uint) (*models.AstrologerWaitingQueue, error) {

	var row models.AstrologerWaitingQueue

	err := r.db.Where("id = ?", id).First(&row).Error

	if err != nil {

		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}

		return nil, err
	}

	return &row, nil
}

func (r *consultationRepository) UpdateQueueEntry(
	tx *gorm.DB,
	id uint,
	updates map[string]interface{},
) error {

	return r.dbOr(tx).
		Model(&models.AstrologerWaitingQueue{}).
		Where("id = ?", id).
		Updates(updates).Error
}

// ListUserQueueEntries returns the customer's active rows, newest first.
func (r *consultationRepository) ListUserQueueEntries(userID uint) ([]models.AstrologerWaitingQueue, error) {

	var rows []models.AstrologerWaitingQueue

	err := r.db.
		Where("user_id = ?", userID).
		Where("status IN ?", constants.ActiveQueueStatuses()).
		Order("id DESC").
		Find(&rows).Error

	return rows, err
}

// ListAstrologersWithQueue is the sweep's work list: every astrologer with a
// row that a timeout or a finished session could still move.
func (r *consultationRepository) ListAstrologersWithQueue(limit int) ([]uint, error) {

	var ids []uint

	statuses := append(constants.ActiveQueueStatuses(), constants.QueueStatusConnected)

	err := r.db.
		Model(&models.AstrologerWaitingQueue{}).
		Where("status IN ?", statuses).
		Distinct("astrologer_id").
		Limit(limit).
		Pluck("astrologer_id", &ids).Error

	return ids, err
}

// CloseQueueForConsultation moves the queue row a consultation was placed
// from, inside the consultation's own transaction. Only rows still in one of
// `from` are touched, so a row that already moved on keeps its status.
func (r *consultationRepository) CloseQueueForConsultation(
	tx *gorm.DB,
	consultationID uint,
	from []string,
	updates map[string]interface{},
) error {

	return r.dbOr(tx).
		Model(&models.AstrologerWaitingQueue{}).
		Where("consultation_id = ?", consultationID).
		Where("status IN ?", from).
		Updates(updates).Error
}
