package repositories_astrologer

import (
	config "astrology-api/configs"
	"astrology-api/constants"
	models "astrology-api/models/astrologermodel"
	"errors"
	"strconv"
	"strings"
	"time"

	"gorm.io/gorm"
)

type dashboardRepository struct {
	db *gorm.DB
}

func NewDashboardRepository() DashboardRepository {
	return &dashboardRepository{
		db: config.DB,
	}
}

func NewDashboardRepositoryWithDB(db *gorm.DB) DashboardRepository {
	return &dashboardRepository{
		db: db,
	}
}

// GetDashboard retrieves dashboard data for an astrologer.
func (r *dashboardRepository) GetDashboard(userID uint) (*DashboardData, error) {

	astrologer, err := r.GetAstrologerByUserID(userID)
	if err != nil {
		return nil, err
	}

	status, err := r.GetStatus(astrologer.ID)
	if err != nil {
		return nil, err
	}

	stat, err := r.GetTodayStatistics(astrologer.ID)
	if err != nil {
		return nil, err
	}

	offer, err := r.GetOffer(astrologer.ID)
	if err != nil {
		return nil, err
	}

	if err != nil {
		return nil, err
	}

	wallet, err := r.GetWalletBalance(userID)
	if err != nil {
		return nil, err
	}
	restTimes, err := r.GetRestTime(astrologer.ID)
	return &DashboardData{
		Astrologer:    astrologer,
		Status:        status,
		Statistics:    stat,
		Offer:         offer,
		RestTime:      restTimes,
		WalletBalance: wallet,
	}, nil
}

// GetAstrologerByUserID retrieves an astrologer by the linked user ID.
func (r *dashboardRepository) GetAstrologerByUserID(userID uint) (*models.Astrologer, error) {

	var astrologer models.Astrologer

	err := r.db.
		Where("userId = ?", userID).
		First(&astrologer).Error

	if err != nil {

		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("astrologer not found")
		}

		return nil, err
	}

	return &astrologer, nil
}

func (r *dashboardRepository) GetAstrologerByID(astrologerID uint) (*models.Astrologer, error) {

	var astrologer models.Astrologer

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

	if err != nil {

		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("astrologer not found")
		}

		return nil, err
	}

	return &astrologer, nil
}

func (r *dashboardRepository) today() string {
	return time.Now().Format("2006-01-02")
}

func (r *dashboardRepository) exists(model interface{}, query string, args ...interface{}) (bool, error) {

	var count int64

	err := r.db.
		Model(model).
		Where(query, args...).
		Count(&count).Error

	if err != nil {
		return false, err
	}

	return count > 0, nil
}

func (r *dashboardRepository) save(value interface{}) error {
	return r.db.Save(value).Error
}

func (r *dashboardRepository) create(value interface{}) error {
	return r.db.Create(value).Error
}

func (r *dashboardRepository) delete(model interface{}, id uint) error {

	return r.db.
		Delete(model, id).
		Error
}

func (r *dashboardRepository) GetStatus(astrologerID uint) (*models.AstrologerStatus, error) {

	var status models.AstrologerStatus

	err := r.db.
		Where("astrologer_id = ?", astrologerID).
		First(&status).Error

	if err != nil {

		if errors.Is(err, gorm.ErrRecordNotFound) {
			var astrologer models.Astrologer

			if err := r.db.
				Where("id = ?", astrologerID).
				First(&astrologer).Error; err != nil {

				return nil, err
			}

			now := time.Now()

			status = models.AstrologerStatus{
				UserID:        astrologer.UserID,
				AstrologerID:  astrologer.ID,
				CurrentStatus: "OFFLINE",
				IsOnline:      false,
				IsChatOnline:  false,
				IsCallOnline:  false,
				IsBusy:        false,
				QuickDND:      false,
				LastActiveAt:  &now,
				IsActive:      true,
				IsDelete:      false,
				CreatedAt:     &now,
				UpdatedAt:     &now,
			}

			if err := r.db.Create(&status).Error; err != nil {
				return nil, err
			}

			return &status, nil
		}

		return nil, err
	}

	return &status, nil
}

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

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

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

func (r *dashboardRepository) UpdateStatus(status *models.AstrologerStatus) error {

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

	return r.db.Save(status).Error
}

func (r *dashboardRepository) UpdateOnlineStatus(astrologerID uint, online bool) error {

	now := time.Now()

	updateData := map[string]interface{}{
		"is_online":      online,
		"last_active_at": now,
	}

	if online {

		updateData["current_status"] = "online"
		updateData["online_at"] = now

	} else {

		updateData["current_status"] = "offline"
		updateData["offline_at"] = now
	}

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

func (r *dashboardRepository) UpdateChatStatus(astrologerID uint, online bool) error {

	now := time.Now()

	updateData := map[string]interface{}{
		"is_chat_online": online,
		"last_active_at": now,
	}

	if online {
		updateData["chat_online_at"] = now
	}

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

func (r *dashboardRepository) UpdateCallStatus(astrologerID uint, online bool) error {

	now := time.Now()

	updateData := map[string]interface{}{
		"is_call_online": online,
		"last_active_at": now,
	}

	if online {
		updateData["call_online_at"] = now
	}

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

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

	now := time.Now()

	updateData := map[string]interface{}{
		"quick_dnd":      enabled,
		"last_active_at": now,
	}

	if enabled {

		updateData["current_status"] = "dnd"
		updateData["dnd_started_at"] = now

	} else {

		updateData["current_status"] = "online"
		updateData["dnd_ended_at"] = now
	}

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

func (r *dashboardRepository) GetTodayStatistics(astrologerID uint) (*models.AstrologerDailyStatistics, error) {

	var statistics models.AstrologerDailyStatistics

	// Today's date (00:00:00)
	now := time.Now()
	today := time.Date(now.Year(), now.Month(), now.Day(), 0,
		0,
		0,
		0,
		now.Location(),
	)

	err := r.db.
		Where("astrologer_id = ?", astrologerID).
		Where("statistic_date = ?", today).
		Where("is_active = ?", true).
		Where("is_delete = ?", false).
		First(&statistics).Error

	if err != nil {

		if errors.Is(err, gorm.ErrRecordNotFound) {

			statistics = models.AstrologerDailyStatistics{
				AstrologerID:     astrologerID,
				StatisticDate:    today,
				OnlineMinutes:    0,
				TotalCalls:       0,
				CompletedCalls:   0,
				MissedCalls:      0,
				CancelledCalls:   0,
				TotalChats:       0,
				CompletedChats:   0,
				MissedChats:      0,
				CancelledChats:   0,
				TotalCallMinutes: 0,
				TotalChatMinutes: 0,
				CallEarning:      0,
				ChatEarning:      0,
				ReportEarning:    0,
				GiftEarning:      0,
				TotalEarning:     0,
				AverageRating:    0,
				TotalReviews:     0,
				AcceptanceRate:   0,
				ResponseRate:     0,
				IsActive:         true,
				IsDelete:         false,
			}

			if err := r.db.Create(&statistics).Error; err != nil {
				return nil, err
			}

			return &statistics, nil
		}

		return nil, err
	}

	return &statistics, nil

}

func (r *dashboardRepository) CreateTodayStatistics(statistics *models.AstrologerDailyStatistics) error {

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

	now := time.Now()

	statistics.StatisticDate = time.Date(
		now.Year(),
		now.Month(),
		now.Day(),
		0,
		0,
		0,
		0,

		now.Location(),
	)

	statistics.IsActive = true
	statistics.IsDelete = false

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

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

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

func (r *dashboardRepository) UpdateTodayStatistics(statistics *models.AstrologerDailyStatistics) error {

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

	now := time.Now()
	statistics.UpdatedAt = &now

	return r.db.Model(&models.AstrologerDailyStatistics{}).
		Where("id = ?", statistics.ID).
		Updates(map[string]interface{}{
			"online_minutes":     statistics.OnlineMinutes,
			"total_calls":        statistics.TotalCalls,
			"completed_calls":    statistics.CompletedCalls,
			"missed_calls":       statistics.MissedCalls,
			"cancelled_calls":    statistics.CancelledCalls,
			"total_chats":        statistics.TotalChats,
			"completed_chats":    statistics.CompletedChats,
			"missed_chats":       statistics.MissedChats,
			"cancelled_chats":    statistics.CancelledChats,
			"total_call_minutes": statistics.TotalCallMinutes,
			"total_chat_minutes": statistics.TotalChatMinutes,
			"call_earning":       statistics.CallEarning,
			"chat_earning":       statistics.ChatEarning,
			"report_earning":     statistics.ReportEarning,
			"gift_earning":       statistics.GiftEarning,
			"total_earning":      statistics.TotalEarning,
			"average_rating":     statistics.AverageRating,
			"total_reviews":      statistics.TotalReviews,
			"acceptance_rate":    statistics.AcceptanceRate,
			"response_rate":      statistics.ResponseRate,
			"is_active":          statistics.IsActive,
			"is_delete":          statistics.IsDelete,
			"modified_by":        statistics.ModifiedBy,
			"updated_at":         statistics.UpdatedAt,
		}).Error
}

func (r *dashboardRepository) GetWalletBalance(userID uint) (float64, error) {

	var wallet models.UserWallet

	err := r.db.
		Where("userid = ?", userID).
		First(&wallet).Error

	if err != nil {

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

		return 0, err
	}

	return *wallet.Amount, nil
}

func (r *dashboardRepository) GetOffer(astrologerID uint) (*models.AstrologerOfferSetting, error) {

	var offer models.AstrologerOfferSetting

	err := r.db.
		Where("astrologer_id = ?", astrologerID).
		Where("is_active = ?", true).
		Where("is_delete = ?", false).
		First(&offer).Error

	if err != nil {

		if errors.Is(err, gorm.ErrRecordNotFound) {

			now := time.Now()

			offer = models.AstrologerOfferSetting{
				AstrologerID:        astrologerID,
				PO5Enabled:          false,
				PO5Amount:           0,
				FreeChatEnabled:     false,
				FreeChatMinutes:     0,
				FreeCallEnabled:     false,
				FreeCallMinutes:     0,
				CampaignEnabled:     false,
				CampaignTitle:       "",
				CampaignDescription: "",
				Priority:            0,
				IsActive:            true,
				IsDelete:            false,
				CreatedAt:           &now,
				UpdatedAt:           &now,
			}

			if err := r.db.Create(&offer).Error; err != nil {
				return nil, err
			}

			return &offer, nil
		}

		return nil, err
	}

	return &offer, nil
}

func (r *dashboardRepository) CreateOffer(offer *models.AstrologerOfferSetting) error {

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

	now := time.Now()

	offer.IsActive = true
	offer.IsDelete = false

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

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

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

func (r *dashboardRepository) UpdateOffer(offer *models.AstrologerOfferSetting) error {

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

	now := time.Now()
	offer.UpdatedAt = &now

	return r.db.
		Model(&models.AstrologerOfferSetting{}).
		Where("id = ?", offer.ID).
		Updates(map[string]interface{}{
			"po5_enabled":          offer.PO5Enabled,
			"po5_amount":           offer.PO5Amount,
			"free_chat_enabled":    offer.FreeChatEnabled,
			"free_chat_minutes":    offer.FreeChatMinutes,
			"free_call_enabled":    offer.FreeCallEnabled,
			"free_call_minutes":    offer.FreeCallMinutes,
			"campaign_enabled":     offer.CampaignEnabled,
			"campaign_title":       offer.CampaignTitle,
			"campaign_description": offer.CampaignDescription,
			"start_date":           offer.StartDate,
			"end_date":             offer.EndDate,
			"priority":             offer.Priority,
			"is_active":            offer.IsActive,
			"is_delete":            offer.IsDelete,
			"modified_by":          offer.ModifiedBy,
			"updated_at":           offer.UpdatedAt,
		}).Error
}

func (r *dashboardRepository) GetRestTime(astrologerID uint) ([]models.AstrologerRestTime, error) {

	var restTimes []models.AstrologerRestTime

	err := r.db.
		Where("astrologer_id = ?", astrologerID).
		Where("is_active = ?", true).
		Where("is_delete = ?", false).
		Order("id DESC").
		Find(&restTimes).Error

	if err != nil {
		return nil, err
	}

	return restTimes, nil
}

func (r *dashboardRepository) CreateRestTime(restTime *models.AstrologerRestTime) error {

	if restTime == nil {
		return errors.New("rest time cannot be nil")
	}

	now := time.Now()

	restTime.IsActive = true
	restTime.IsDelete = false

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

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

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

func (r *dashboardRepository) UpdateRestTime(restTime *models.AstrologerRestTime) error {

	if restTime == nil {
		return errors.New("rest time cannot be nil")
	}

	now := time.Now()
	restTime.UpdatedAt = &now

	return r.db.
		Model(&models.AstrologerRestTime{}).
		Where("id = ?", restTime.ID).
		Updates(map[string]interface{}{
			"title":       restTime.Title,
			"rest_type":   restTime.RestType,
			"from_time":   restTime.FromTime,
			"to_time":     restTime.ToTime,
			"repeat_type": restTime.RepeatType,
			"repeat_days": restTime.RepeatDays,
			"timezone":    restTime.Timezone,
			"is_active":   restTime.IsActive,
			"is_delete":   restTime.IsDelete,
			"modified_by": restTime.ModifiedBy,
			"updated_at":  restTime.UpdatedAt,
		}).Error
}

func (r *dashboardRepository) DeleteRestTime(restTimeID uint) error {

	now := time.Now()

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

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

	err := r.db.
		Where("astrologer_id = ?", astrologerID).
		Order("created_at desc").
		Limit(limit).
		Find(&logs).Error

	if err != nil {
		return nil, err
	}

	return logs, nil
}

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

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

	now := time.Now()

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

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

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

// CountPendingRequests counts the customers currently ringing this astrologer.
//
// Reads the consultations table directly rather than through the astrologer
// stack's own model: this is a count, the user-side service owns every write
// to that table, and a narrow count has nothing to gain from a struct.
//
// Rows past the ring timeout are excluded rather than counted and then closed.
// The dashboard is a read; expiring a request belongs to the request list, the
// accept path and the sweep, all of which do close it.
func (r *dashboardRepository) CountPendingRequests(
	astrologerID uint,
	ringTimeoutSeconds int,
) (int64, error) {

	// The caller passes 0 to mean "use the configured timeout".
	if ringTimeoutSeconds <= 0 {

		var configured string

		r.db.
			Table("systemflag").
			Select("value").
			Where("name = ?", constants.FlagConsultationRingTimeoutSecs).
			Where("isDelete = ?", false).
			Limit(1).
			Scan(&configured)

		if parsed, err := strconv.Atoi(strings.TrimSpace(configured)); err == nil && parsed > 0 {
			ringTimeoutSeconds = parsed
		} else {
			ringTimeoutSeconds = 60
		}
	}

	cutoff := time.Now().Add(-time.Duration(ringTimeoutSeconds) * time.Second)

	var total int64

	err := r.db.
		Table("consultations").
		Where("astrologerId = ?", astrologerID).
		Where("status = ?", constants.ConsultationStatusRequested).
		Where("isDelete = ?", false).
		Where("COALESCE(requestedAt, created_at) >= ?", cutoff).
		Count(&total).Error

	if err != nil {
		return 0, err
	}

	return total, nil
}
