package handler

import (
	"encoding/json"
	"errors"
	"net/http"
	"strconv"
	"strings"
	"time"
	"xboard-go/internal/database"
	"xboard-go/internal/model"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

// AdminTrafficResetFetch returns traffic reset logs in the shape expected by the admin UI.
func AdminTrafficResetFetch(c *gin.Context) {
	page := parsePositiveInt(c.DefaultQuery("page", "1"), 1)
	perPage := parsePositiveInt(c.DefaultQuery("per_page", "20"), 20)
	if perPage > 1000 {
		perPage = 1000
	}

	query := database.DB.Model(&model.TrafficResetLog{}).
		Preload("User").
		Order("created_at DESC, id DESC")

	if userEmail := strings.TrimSpace(c.Query("user_email")); userEmail != "" {
		query = query.Joins("JOIN v2_user ON v2_user.id = v2_traffic_reset_logs.user_id").
			Where("v2_user.email LIKE ?", "%"+userEmail+"%")
	}

	if resetType := strings.TrimSpace(c.Query("reset_type")); resetType != "" {
		query = query.Where("v2_traffic_reset_logs.reset_type = ?", resetType)
	}

	if triggerSource := strings.TrimSpace(c.Query("trigger_source")); triggerSource != "" {
		query = query.Where("v2_traffic_reset_logs.trigger_source = ?", triggerSource)
	}

	if keyword := strings.TrimSpace(c.Query("keyword")); keyword != "" {
		query = query.Joins("LEFT JOIN v2_user AS keyword_user ON keyword_user.id = v2_traffic_reset_logs.user_id").
			Where("keyword_user.email LIKE ? OR CAST(v2_traffic_reset_logs.user_id AS CHAR) = ?", "%"+keyword+"%", keyword)
	}

	if startDate := strings.TrimSpace(c.Query("start_date")); startDate != "" {
		if ts, ok := parseDateStart(startDate); ok {
			query = query.Where("v2_traffic_reset_logs.created_at >= ?", ts)
		}
	}

	if endDate := strings.TrimSpace(c.Query("end_date")); endDate != "" {
		if ts, ok := parseDateEnd(endDate); ok {
			query = query.Where("v2_traffic_reset_logs.created_at <= ?", ts)
		}
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to count traffic reset logs")
		return
	}

	var logs []model.TrafficResetLog
	if err := query.Offset((page - 1) * perPage).Limit(perPage).Find(&logs).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch traffic reset logs")
		return
	}

	items := make([]gin.H, 0, len(logs))
	for _, log := range logs {
		items = append(items, serializeTrafficResetLog(log))
	}

	c.JSON(http.StatusOK, gin.H{
		"data":  items,
		"total": total,
	})
}

// AdminTrafficResetUser manually resets a user's traffic and records the action in logs.
func AdminTrafficResetUser(c *gin.Context) {
	var payload struct {
		UserID int    `json:"user_id"`
		Reason string `json:"reason"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil || payload.UserID <= 0 {
		Fail(c, http.StatusBadRequest, "user_id is required")
		return
	}

	now := time.Now().Unix()
	var user model.User

	err := database.DB.Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("id = ?", payload.UserID).First(&user).Error; err != nil {
			return err
		}

		oldUpload := int64(user.U)
		oldDownload := int64(user.D)
		oldTotal := oldUpload + oldDownload

		updates := map[string]any{
			"u":             0,
			"d":             0,
			"last_reset_at": now,
			"reset_count":   gorm.Expr("reset_count + 1"),
			"updated_at":    now,
		}
		if err := tx.Model(&model.User{}).Where("id = ?", user.ID).Updates(updates).Error; err != nil {
			return err
		}

		metadata, err := marshalTrafficResetMetadata(strings.TrimSpace(payload.Reason))
		if err != nil {
			return err
		}

		entry := model.TrafficResetLog{
			UserID:        user.ID,
			ResetType:     "manual",
			ResetTime:     now,
			OldUpload:     oldUpload,
			OldDownload:   oldDownload,
			OldTotal:      oldTotal,
			NewUpload:     0,
			NewDownload:   0,
			NewTotal:      0,
			TriggerSource: "manual",
			Metadata:      metadata,
			CreatedAt:     now,
			UpdatedAt:     now,
		}
		return tx.Create(&entry).Error
	})
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			Fail(c, http.StatusBadRequest, "user does not exist")
			return
		}
		Fail(c, http.StatusInternalServerError, "failed to reset traffic")
		return
	}

	Success(c, true)
}

// AdminTrafficResetUserHistory returns summary and recent reset history for one user.
func AdminTrafficResetUserHistory(c *gin.Context) {
	userID := parsePositiveInt(c.Param("id"), 0)
	if userID <= 0 {
		Fail(c, http.StatusBadRequest, "user id is required")
		return
	}

	limit := parsePositiveInt(c.DefaultQuery("limit", "10"), 10)
	if limit > 100 {
		limit = 100
	}

	var user model.User
	if err := database.DB.Where("id = ?", userID).First(&user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			Fail(c, http.StatusBadRequest, "user does not exist")
			return
		}
		Fail(c, http.StatusInternalServerError, "failed to load user")
		return
	}

	var logs []model.TrafficResetLog
	if err := database.DB.Where("user_id = ?", userID).
		Order("created_at DESC, id DESC").
		Limit(limit).
		Find(&logs).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "failed to fetch traffic reset history")
		return
	}

	history := make([]gin.H, 0, len(logs))
	for _, log := range logs {
		history = append(history, serializeTrafficResetLog(log))
	}

	Success(c, gin.H{
		"user": gin.H{
			"id":            user.ID,
			"email":         user.Email,
			"reset_count":   user.ResetCount,
			"last_reset_at": int64Value(user.LastResetAt),
			"next_reset_at": int64Value(user.NextResetAt),
		},
		"history": history,
	})
}

func serializeTrafficResetLog(log model.TrafficResetLog) gin.H {
	email := ""
	if log.User != nil {
		email = log.User.Email
	}

	return gin.H{
		"id":                  log.ID,
		"user_id":             log.UserID,
		"user_email":          email,
		"reset_type":          log.ResetType,
		"reset_type_name":     trafficResetTypeName(log.ResetType),
		"reset_time":          log.ResetTime,
		"trigger_source":      log.TriggerSource,
		"trigger_source_name": trafficResetTriggerSourceName(log.TriggerSource),
		"old_traffic": gin.H{
			"upload":    log.OldUpload,
			"download":  log.OldDownload,
			"total":     log.OldTotal,
			"formatted": formatTrafficValue(log.OldTotal),
		},
		"new_traffic": gin.H{
			"upload":    log.NewUpload,
			"download":  log.NewDownload,
			"total":     log.NewTotal,
			"formatted": formatTrafficValue(log.NewTotal),
		},
		"metadata":   decodeTrafficResetMetadata(log.Metadata),
		"created_at": log.CreatedAt,
		"updated_at": log.UpdatedAt,
	}
}

func trafficResetTypeName(resetType string) string {
	switch resetType {
	case "manual":
		return "Manual"
	case "monthly":
		return "Monthly"
	case "yearly":
		return "Yearly"
	case "first_day_month":
		return "First Day of Month"
	case "first_day_year":
		return "First Day of Year"
	default:
		if resetType == "" {
			return "-"
		}
		return strings.ReplaceAll(resetType, "_", " ")
	}
}

func trafficResetTriggerSourceName(source string) string {
	switch source {
	case "manual":
		return "Manual"
	case "cron":
		return "Cron"
	case "auto":
		return "Auto"
	case "order":
		return "Order"
	case "gift_card":
		return "Gift Card"
	default:
		if source == "" {
			return "-"
		}
		return strings.ReplaceAll(source, "_", " ")
	}
}

func formatTrafficValue(bytes int64) string {
	if bytes <= 0 {
		return "0 B"
	}

	units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
	size := float64(bytes)
	unitIndex := 0
	for size >= 1024 && unitIndex < len(units)-1 {
		size /= 1024
		unitIndex++
	}

	if size >= 100 {
		return strconv.FormatFloat(size, 'f', 0, 64) + " " + units[unitIndex]
	}
	formatted := strconv.FormatFloat(size, 'f', 2, 64)
	formatted = strings.TrimRight(strings.TrimRight(formatted, "0"), ".")
	return formatted + " " + units[unitIndex]
}

func parseDateStart(raw string) (int64, bool) {
	date, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(raw), time.Local)
	if err != nil {
		return 0, false
	}
	return date.Unix(), true
}

func parseDateEnd(raw string) (int64, bool) {
	date, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(raw), time.Local)
	if err != nil {
		return 0, false
	}
	return date.Add(24*time.Hour - time.Second).Unix(), true
}

func marshalTrafficResetMetadata(reason string) (string, error) {
	if reason == "" {
		return "", nil
	}

	payload, err := json.Marshal(map[string]string{
		"reason": reason,
	})
	if err != nil {
		return "", err
	}
	return string(payload), nil
}

func decodeTrafficResetMetadata(raw string) any {
	if strings.TrimSpace(raw) == "" {
		return nil
	}

	var value any
	if err := json.Unmarshal([]byte(raw), &value); err != nil {
		return raw
	}
	return value
}
