package repositories_astrologer

import (
	config "astrology-api/configs"
	models "astrology-api/models/astrologermodel"
	"errors"
	"fmt"
	"time"

	"gorm.io/gorm"
)

type statusRepository struct {
	db *gorm.DB
}

func NewStatusRepository() StatusRepository {
	return &statusRepository{
		db: config.DB,
	}
}

func NewStatusRepositoryWithDB(db *gorm.DB) StatusRepository {
	return &statusRepository{
		db: db,
	}
}

// GetStatus reads the astrologer_status row.
//
// A missing row is reported as a wrapped gorm.ErrRecordNotFound, because every
// caller in the dashboard service writes
//
//	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err }
//	if status == nil { ...create the default row... }
//
// and this used to return errors.New("astrologer status not found"), which no
// longer matched that sentinel. The guard therefore returned early, the
// create-the-default branch was dead code, and every status endpoint —
// online, chat, call, busy, quick DND, rest time — failed outright for an
// astrologer who had no status row yet. astrologer_status is empty on this
// database, so that was every astrologer.
func (r *statusRepository) GetStatus(astrologerID uint) (*models.AstrologerStatus, error) {
	var status models.AstrologerStatus
	err := r.db.
		Where("astrologer_id = ?", astrologerID).
		Where("is_delete = ?", false).
		First(&status).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fmt.Errorf("astrologer status not found: %w", gorm.ErrRecordNotFound)
		}
		return nil, err
	}
	return &status, nil
}

func (r *statusRepository) CreateStatus(status *models.AstrologerStatus) error {

	if status == nil {
		return errors.New("status cannot be nil")
	}
	now := time.Now()
	status.IsActive = true
	status.IsDelete = false
	if status.CreatedAt == nil {
		status.CreatedAt = &now
	}
	if status.UpdatedAt == nil {
		status.UpdatedAt = &now
	}
	return r.db.Create(status).Error
}

func (r *statusRepository) UpdateStatus(status *models.AstrologerStatus) error {
	if status == nil {
		return errors.New("status cannot be nil")
	}
	now := time.Now()
	status.UpdatedAt = &now
	return r.db.
		Model(&models.AstrologerStatus{}).
		Where("id = ?", status.ID).
		Updates(map[string]interface{}{
			"is_online":      status.IsOnline,
			"is_chat_online": status.IsChatOnline,
			"is_call_online": status.IsCallOnline,
			"is_busy":        status.IsBusy,
			"quick_dnd":      status.QuickDND,
			"current_status": status.CurrentStatus,
			"online_at":      status.OnlineAt,
			"offline_at":     status.OfflineAt,
			"chat_online_at": status.ChatOnlineAt,
			"call_online_at": status.CallOnlineAt,
			"last_active_at": status.LastActiveAt,
			"modified_by":    status.ModifiedBy,
			"updated_at":     status.UpdatedAt,
			"is_active":      status.IsActive,
			"is_delete":      status.IsDelete,
		}).Error
}

func (r *statusRepository) DeleteStatus(astrologerID uint) error {

	now := time.Now()

	return r.db.
		Model(&models.AstrologerStatus{}).
		Where("astrologer_id = ?", astrologerID).
		Updates(map[string]interface{}{
			"is_active":  false,
			"is_delete":  true,
			"updated_at": &now,
		}).Error
}

func (r *statusRepository) UpdateOnlineStatus(
	astrologerID uint,
	isOnline bool,
) error {

	now := time.Now()

	updates := map[string]interface{}{
		"is_online":      isOnline,
		"last_active_at": &now,
		"updated_at":     &now,
	}

	if isOnline {
		updates["online_at"] = &now
		updates["offline_at"] = nil
		updates["current_status"] = "online"
	} else {
		updates["offline_at"] = &now
		updates["current_status"] = "offline"
	}

	return r.db.Transaction(func(tx *gorm.DB) error {

		if err := tx.
			Model(&models.AstrologerStatus{}).
			Where("astrologer_id = ?", astrologerID).
			Updates(updates).Error; err != nil {
			return err
		}

		return tx.
			Model(&models.Astrologer{}).
			Where("id = ?", astrologerID).
			Updates(astrologerPresence(isOnline)).Error
	})
}

// SetAstrologerPresenceByUserID mirrors online/offline onto the astrologers
// row (status + is_available) for login and logout. Matches no row, and is a
// no-op, for an account that has not saved profile step 1 yet.
func SetAstrologerPresenceByUserID(tx *gorm.DB, userID uint, isOnline bool) error {

	return tx.
		Model(&models.Astrologer{}).
		Where("userId = ?", userID).
		Updates(astrologerPresence(isOnline)).Error
}

func astrologerPresence(isOnline bool) map[string]interface{} {

	if isOnline {
		return map[string]interface{}{"status": "ONLINE", "is_available": true}
	}

	return map[string]interface{}{"status": "OFFLINE", "is_available": false}
}

func (r *statusRepository) UpdateChatStatus(astrologerID uint, isOnline bool) error {

	now := time.Now()

	updates := map[string]interface{}{
		"is_chat_online": isOnline,
		"last_active_at": &now,
		"updated_at":     &now,
	}

	if isOnline {
		updates["chat_online_at"] = &now
	}

	return r.db.
		Model(&models.AstrologerStatus{}).
		Where("astrologer_id = ?", astrologerID).
		Updates(updates).Error
}

func (r *statusRepository) UpdateCallStatus(astrologerID uint, isOnline bool) error {

	now := time.Now()

	updates := map[string]interface{}{
		"is_call_online": isOnline,
		"last_active_at": &now,
		"updated_at":     &now,
	}

	if isOnline {
		updates["call_online_at"] = &now
	}

	return r.db.
		Model(&models.AstrologerStatus{}).
		Where("astrologer_id = ?", astrologerID).
		Updates(updates).Error
}

func (r *statusRepository) UpdateBusyStatus(astrologerID uint, isBusy bool) error {

	now := time.Now()

	updates := map[string]interface{}{
		"is_busy":        isBusy,
		"last_active_at": &now,
		"updated_at":     &now,
	}

	if isBusy {
		updates["busy_at"] = &now
		updates["current_status"] = "busy"
	} else {
		updates["busy_at"] = nil

		// current_status is enum('offline','online','busy','dnd'). "AVAILABLE"
		// is not a member, so clearing busy used to store an empty string and
		// the astrologer showed as neither online nor offline. Coming off busy
		// returns the row to whichever it was, decided in SQL so it cannot go
		// stale between the read and this write.
		updates["current_status"] = gorm.Expr("IF(is_online, 'online', 'offline')")
	}

	return r.db.
		Model(&models.AstrologerStatus{}).
		Where("astrologer_id = ?", astrologerID).
		Updates(updates).Error
}

func (r *statusRepository) UpdateQuickDND(astrologerID uint, enabled bool) error {

	now := time.Now()

	updates := map[string]interface{}{
		"quick_dnd":      enabled,
		"last_active_at": &now,
		"updated_at":     &now,
	}

	if enabled {
		updates["dnd_started_at"] = &now
		updates["dnd_ended_at"] = nil
		updates["current_status"] = "dnd"
	} else {
		updates["dnd_ended_at"] = &now

		// Same as UpdateBusyStatus: "AVAILABLE" is not an enum member, so
		// leaving DND used to blank current_status.
		updates["current_status"] = gorm.Expr("IF(is_online, 'online', 'offline')")
	}

	return r.db.
		Model(&models.AstrologerStatus{}).
		Where("astrologer_id = ?", astrologerID).
		Updates(updates).Error
}

func (r *statusRepository) UpdateCurrentStatus(astrologerID uint, status string) error {

	now := time.Now()

	return r.db.
		Model(&models.AstrologerStatus{}).
		Where("astrologer_id = ?", astrologerID).
		Updates(map[string]interface{}{
			"current_status": status,
			"last_active_at": &now,
			"updated_at":     &now,
		}).Error
}

func (r *statusRepository) UpdateLastActiveTime(astrologerID uint) error {

	now := time.Now()

	return r.db.
		Model(&models.AstrologerStatus{}).
		Where("astrologer_id = ?", astrologerID).
		Update("last_active_at", &now).Error
}

func (r *statusRepository) MarkLogin(astrologerID uint) error {

	now := time.Now()

	return r.db.
		Model(&models.AstrologerStatus{}).
		Where("astrologer_id = ?", astrologerID).
		Updates(map[string]interface{}{
			"is_online":      true,
			"current_status": "online",
			"online_at":      &now,
			"last_active_at": &now,
			"updated_at":     &now,
		}).Error
}

func (r *statusRepository) MarkLogout(astrologerID uint) error {

	now := time.Now()

	return r.db.
		Model(&models.AstrologerStatus{}).
		Where("astrologer_id = ?", astrologerID).
		Updates(map[string]interface{}{
			"is_online":      false,
			"is_chat_online": false,
			"is_call_online": false,
			"is_busy":        false,
			"quick_dnd":      false,
			"current_status": "offline",
			"offline_at":     &now,
			"last_active_at": &now,
			"updated_at":     &now,
		}).Error
}

func (r *statusRepository) CreateStatusLog(
	log *models.AstrologerStatusLog,
) error {

	if log == nil {
		return errors.New("status log cannot be nil")
	}

	now := time.Now()

	log.IsActive = true
	log.IsDelete = false

	if log.CreatedAt == nil {
		log.CreatedAt = &now
	}

	if log.UpdatedAt == nil {
		log.UpdatedAt = &now
	}

	if log.StartedAt.IsZero() {
		log.StartedAt = now
	}

	return r.db.Create(log).Error
}

func (r *statusRepository) GetStatusLogs(
	astrologerID uint,
	limit int,
) ([]models.AstrologerStatusLog, error) {

	var logs []models.AstrologerStatusLog

	query := r.db.
		Where("astrologer_id = ?", astrologerID).
		Where("is_delete = ?", false).
		Order("started_at DESC")

	if limit > 0 {
		query = query.Limit(limit)
	}

	err := query.Find(&logs).Error
	if err != nil {
		return nil, err
	}

	return logs, nil
}
