package points

import (
	"fmt"
	"math"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gothinkster/golang-gin-realworld-example-app/common"
	"github.com/gothinkster/golang-gin-realworld-example-app/models"
)

// GetDashboard returns user points dashboard
// GET /api/points/dashboard?wallet_address=0x...
func GetDashboard(c *gin.Context) {
	db := common.GetDB()

	user, found := getUserByWallet(c)
	if !found {
		c.JSON(http.StatusNotFound, gin.H{
			"success": false,
			"error":   "User not found",
		})
		return
	}

	vipLevel := user.VIPLevel
	if vipLevel < 1 {
		vipLevel = 1
	}

	var currentTier models.VIPTier
	db.Where("tier_level = ?", vipLevel).First(&currentTier)

	var nextTier models.VIPTier
	var pointsToNextTier int64
	var nextTierName string

	if db.Where("tier_level = ?", vipLevel+1).First(&nextTier).Error == nil {
		pointsToNextTier = nextTier.MinPoints - user.TotalPoints
		nextTierName = nextTier.TierName
	} else {
		pointsToNextTier = 0
		nextTierName = "Max Level"
	}

	var season models.Season
	db.Where("status = ?", "active").First(&season)

	now := time.Now()
	timeRemaining := season.EndTime.Sub(now)
	daysRemaining := int(timeRemaining.Hours() / 24)
	hoursRemaining := int(timeRemaining.Hours()) % 24

	var totalUsers int64
	db.Model(&models.User{}).Count(&totalUsers)
	topPercentage := "N/A"
	globalRank := 0
	if user.GlobalRank != nil {
		globalRank = *user.GlobalRank
		if totalUsers > 0 {
			percentage := float64(*user.GlobalRank) / float64(totalUsers) * 100
			topPercentage = fmt.Sprintf("%.0f%%", percentage)
		}
	}

	response := models.DashboardResponse{
		TotalPoints:      user.TotalPoints,
		GlobalRank:       globalRank,
		TopPercentage:    topPercentage,
		MemberTier:       user.MemberTier,
		VIPLevel:         vipLevel,
		PointsToNextTier: pointsToNextTier,
		NextTier:         nextTierName,
		Season: models.SeasonInfo{
			SeasonNumber:   season.SeasonNumber,
			SeasonName:     season.SeasonName,
			IsLive:         season.Status == "active",
			EndTime:        season.EndTime,
			DaysRemaining:  daysRemaining,
			HoursRemaining: hoursRemaining,
		},
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    response,
	})
}

// GetLeaderboard returns top performers leaderboard
// GET /api/points/leaderboard?limit=5&wallet_address=0x...
func GetLeaderboard(c *gin.Context) {
	db := common.GetDB()

	limitStr := c.DefaultQuery("limit", "5")
	limit, _ := strconv.Atoi(limitStr)

	var topUsers []models.User
	db.Where("global_rank IS NOT NULL").
		Order("global_rank ASC").
		Limit(limit).
		Find(&topUsers)

	var leaderboard []models.LeaderboardUser
	for _, u := range topUsers {
		rank := 0
		if u.GlobalRank != nil {
			rank = *u.GlobalRank
		}
		leaderboard = append(leaderboard, models.LeaderboardUser{
			Rank:          rank,
			WalletAddress: formatAddress(u.WalletAddress),
			Points:        u.TotalPoints,
		})
	}

	currentUser, _ := getUserByWallet(c)
	myRank := 0
	if currentUser.GlobalRank != nil {
		myRank = *currentUser.GlobalRank
	}

	response := models.LeaderboardResponse{
		TopUsers: leaderboard,
		MyRank:   myRank,
		MyPoints: currentUser.TotalPoints,
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    response,
	})
}

// GetInviteCode returns user's invite code information
// GET /api/points/invite-code?wallet_address=0x...
func GetInviteCode(c *gin.Context) {
	db := common.GetDB()

	user, found := getUserByWallet(c)
	if !found {
		c.JSON(http.StatusNotFound, gin.H{
			"success": false,
			"error":   "User not found",
		})
		return
	}

	var inviteCode models.InviteCode
	err := db.Where("wallet_address = ?", user.WalletAddress).First(&inviteCode).Error

	if err != nil {
		// Create a system-generated invite code
		code := strings.ToUpper(common.RandString(8))
		defaultExpiry := time.Date(2099, 12, 31, 23, 59, 59, 0, time.UTC)
		inviteCode = models.InviteCode{
			WalletAddress: user.WalletAddress,
			Code:          code,
			MaxUses:       -1,
			UsedCount:     0,
			IsActive:      true,
			ExpiresAt:     models.NullTime{Time: &defaultExpiry},
		}

		if err := db.Create(&inviteCode).Error; err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"success": false,
				"error":   "Failed to create invite code",
			})
			return
		}

		// Also sync to users.invite_code
		db.Model(&user).Update("invite_code", code)
	}

	response := models.InviteCodeResponse{
		Code:      inviteCode.Code,
		UsedCount: inviteCode.UsedCount,
		MaxUses:   inviteCode.MaxUses,
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    response,
	})
}

// BindInvite binds an invite code to current user
// POST /api/points/bind-invite
func BindInvite(c *gin.Context) {
	db := common.GetDB()

	var req struct {
		Code          string `json:"code" binding:"required"`
		Signature     string `json:"signature" binding:"required"`
		WalletAddress string `json:"wallet_address"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"error":   "Invalid request body",
		})
		return
	}

	// Look up user by wallet_address
	var user models.User
	walletAddr := strings.ToLower(strings.TrimSpace(req.WalletAddress))
	if walletAddr == "" {
		// Try query param fallback
		walletAddr = strings.ToLower(strings.TrimSpace(c.Query("wallet_address")))
	}

	if walletAddr == "" || db.Where("wallet_address = ?", walletAddr).First(&user).Error != nil {
		c.JSON(http.StatusNotFound, gin.H{
			"success": false,
			"error":   "User not found. Please connect your wallet first.",
		})
		return
	}

	// Check if user already has a referrer
	if user.ReferrerWallet != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"error":   "Already bound to a referrer",
		})
		return
	}

	// Find invite code
	var inviteCode models.InviteCode
	if err := db.Where("code = ? AND is_active = ?", req.Code, true).First(&inviteCode).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{
			"success": false,
			"error":   "Invalid invite code",
		})
		return
	}

	// Check if max uses reached
	if inviteCode.MaxUses != -1 && inviteCode.UsedCount >= inviteCode.MaxUses {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"error":   "Invite code max uses reached",
		})
		return
	}

	// Prevent self-referral
	if inviteCode.WalletAddress == user.WalletAddress {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"error":   "Cannot use your own invite code",
		})
		return
	}

	// Create invitation record
	invitation := models.Invitation{
		ReferrerWallet:       inviteCode.WalletAddress,
		RefereeWallet:        user.WalletAddress,
		InviteCode:           req.Code,
		BindSignature:        req.Signature,
		Status:               "active",
		ReferrerRewardPoints: 100,
		RefereeRewardPoints:  50,
		BoundAt:              time.Now(),
	}

	if err := db.Create(&invitation).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"success": false,
			"error":   "Failed to bind invite code",
		})
		return
	}

	// Update user referrer
	db.Model(&user).Update("referrer_wallet", inviteCode.WalletAddress)

	// Update invite code used count
	db.Model(&inviteCode).Update("used_count", inviteCode.UsedCount+1)

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "Invite code bound successfully",
	})
}

// GetTeamTVL returns team TVL statistics
// GET /api/points/team?wallet_address=0x...
func GetTeamTVL(c *gin.Context) {
	db := common.GetDB()

	user, _ := getUserByWallet(c)

	var team models.UserTeam
	if err := db.Where("wallet_address = ?", user.WalletAddress).First(&team).Error; err != nil {
		c.JSON(http.StatusOK, gin.H{
			"success": true,
			"data": models.TeamTVLResponse{
				CurrentTVL:      "$0",
				TargetTVL:       "$10M",
				ProgressPercent: 0,
				TotalMembers:    0,
				Roles:           []models.RoleCount{},
			},
		})
		return
	}

	progressPercent := 0.0
	if team.TeamTargetTVLUSD > 0 {
		progressPercent = (team.TeamTVLUSD / team.TeamTargetTVLUSD) * 100
	}

	response := models.TeamTVLResponse{
		CurrentTVL:      formatUSD(team.TeamTVLUSD),
		TargetTVL:       formatUSD(team.TeamTargetTVLUSD),
		ProgressPercent: math.Round(progressPercent*100) / 100,
		TotalMembers:    team.TeamMembersCount,
		Roles: []models.RoleCount{
			{
				Icon:    "/icons/user/icon-whale.svg",
				Label:   "Whales",
				Current: team.WhalesCount,
				Target:  team.WhalesTarget,
			},
			{
				Icon:    "/icons/user/icon-trader.svg",
				Label:   "Traders",
				Current: team.TradersCount,
				Target:  team.TradersTarget,
			},
			{
				Icon:    "/icons/user/icon-user.svg",
				Label:   "Users",
				Current: team.UsersCount,
				Target:  team.UsersTarget,
			},
		},
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    response,
	})
}

// GetActivities returns user points activities
// GET /api/points/activities?wallet_address=0x...&type=all&page=1&pageSize=10
func GetActivities(c *gin.Context) {
	db := common.GetDB()

	activityType := c.DefaultQuery("type", "all")
	pageStr := c.DefaultQuery("page", "1")
	pageSizeStr := c.DefaultQuery("pageSize", "10")

	page, _ := strconv.Atoi(pageStr)
	pageSize, _ := strconv.Atoi(pageSizeStr)
	offset := (page - 1) * pageSize

	user, _ := getUserByWallet(c)

	query := db.Model(&models.UserPointsRecord{}).Where("wallet_address = ?", user.WalletAddress)

	if activityType == "referrals" {
		query = query.Where("source_type = ?", "invitation")
	} else if activityType == "deposits" {
		query = query.Where("source_type IN ?", []string{"deposit", "holding"})
	}

	var total int64
	query.Count(&total)

	var records []models.UserPointsRecord
	query.Order("created_at DESC").
		Limit(pageSize).
		Offset(offset).
		Find(&records)

	activities := make([]models.ActivityRecord, 0)
	for _, record := range records {
		activity := models.ActivityRecord{
			Type:        record.SourceType,
			UserAddress: formatAddress(user.WalletAddress),
			Points:      record.PointsChange,
			CreatedAt:   record.CreatedAt,
		}

		if record.SourceType == "invitation" && record.SourceID != nil {
			var invitation models.Invitation
			if db.Where("id = ?", *record.SourceID).First(&invitation).Error == nil {
				activity.FriendAddress = formatAddress(invitation.RefereeWallet)
				activity.InviteCode = invitation.InviteCode
			}
		}

		activities = append(activities, activity)
	}

	totalPages := int(math.Ceil(float64(total) / float64(pageSize)))

	response := models.ActivitiesResponse{
		Activities: activities,
		Pagination: models.PaginationInfo{
			Page:      page,
			PageSize:  pageSize,
			Total:     int(total),
			TotalPage: totalPages,
		},
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data":    response,
	})
}

// Helper function to format wallet address for display
func formatAddress(address string) string {
	if len(address) < 10 {
		return address
	}
	return address[:6] + "..." + address[len(address)-4:]
}

// Helper function to format USD values
func formatUSD(value float64) string {
	if value >= 1000000 {
		return fmt.Sprintf("$%.1fM", value/1000000)
	} else if value >= 1000 {
		return fmt.Sprintf("$%.1fK", value/1000)
	}
	return fmt.Sprintf("$%.2f", value)
}

// getUserByWallet looks up a user by wallet_address query param,
// falls back to demo user if not provided
func getUserByWallet(c *gin.Context) (models.User, bool) {
	db := common.GetDB()
	var user models.User
	walletAddress := strings.ToLower(strings.TrimSpace(c.Query("wallet_address")))
	if walletAddress != "" {
		if err := db.Where("wallet_address = ?", walletAddress).First(&user).Error; err == nil {
			return user, true
		}
	}
	// Fall back to first demo user
	err := db.Order("created_at ASC").First(&user).Error
	return user, err == nil
}

// walletAddressToInt64 is no longer needed (no auth_user_id in new schema)
// kept for reference only

// WalletRegister creates or finds a user by wallet address
// POST /api/points/wallet-register
func WalletRegister(c *gin.Context) {
	db := common.GetDB()

	var req struct {
		WalletAddress string `json:"wallet_address"`
	}
	if err := c.ShouldBindJSON(&req); err != nil || strings.TrimSpace(req.WalletAddress) == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"error":   "wallet_address is required",
		})
		return
	}

	walletAddress := strings.ToLower(strings.TrimSpace(req.WalletAddress))

	// Check if user already exists
	var user models.User
	err := db.Where("wallet_address = ?", walletAddress).First(&user).Error
	if err != nil {
		// Create new user with system-generated invite code
		code := strings.ToUpper(common.RandString(8))
		user = models.User{
			WalletAddress: walletAddress,
			InviteCode:    code,
			MemberTier:    "Bronze",
			VIPLevel:      1,
			TotalPoints:   0,
		}
		if createErr := db.Create(&user).Error; createErr != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"success": false,
				"error":   "Failed to create user",
			})
			return
		}

		// Create invite_codes record
		defaultExpiry := time.Date(2099, 12, 31, 23, 59, 59, 0, time.UTC)
		inviteCodeRecord := models.InviteCode{
			WalletAddress: walletAddress,
			Code:          code,
			MaxUses:       -1,
			UsedCount:     0,
			IsActive:      true,
			ExpiresAt:     models.NullTime{Time: &defaultExpiry},
		}
		db.Create(&inviteCodeRecord)
	}

	// Get invite code from invite_codes table
	var inviteCodeRecord models.InviteCode
	inviteCode := user.InviteCode
	usedCount := 0
	if db.Where("wallet_address = ?", user.WalletAddress).First(&inviteCodeRecord).Error == nil {
		inviteCode = inviteCodeRecord.Code
		usedCount = inviteCodeRecord.UsedCount
	}

	globalRank := 0
	if user.GlobalRank != nil {
		globalRank = *user.GlobalRank
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"data": gin.H{
			"walletAddress": user.WalletAddress,
			"inviteCode":    inviteCode,
			"usedCount":     usedCount,
			"memberTier":    user.MemberTier,
			"vipLevel":      user.VIPLevel,
			"totalPoints":   user.TotalPoints,
			"globalRank":    globalRank,
		},
	})
}
