package scoresaber

import (
	"encoding/json"
	"fmt"
	"image"
	"image/color"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"git.lxtend.com/lixiangwuxian/qqbot/util"
	"github.com/fogleman/gg"
	"github.com/google/uuid"
	"github.com/nfnt/resize"
	"golang.org/x/image/font/opentype"
)

var (
	imageCache     = make(map[string]image.Image)
	imageCacheLock sync.RWMutex
)

func init() {
	util.AddCycleTask("bs50_image_cache_cleaner", 10*time.Minute, 10*time.Minute, func() {
		imageCacheLock.Lock()
		defer imageCacheLock.Unlock()
		for k := range imageCache {
			delete(imageCache, k)
		}
	})
}

func loadImageFromCache(path string) (image.Image, error) {
	imageCacheLock.RLock()
	if img, exists := imageCache[path]; exists {
		imageCacheLock.RUnlock()
		return img, nil
	}
	imageCacheLock.RUnlock()

	img, err := gg.LoadImage(path)
	if err != nil {
		return nil, err
	}

	imageCacheLock.Lock()
	imageCache[path] = img
	imageCacheLock.Unlock()

	return img, nil
}

// SongData 代表一首歌曲的数据
type SongData struct {
	JacketPath      string
	Title           string
	Beatmapid       string
	Difficulty      string
	DifficultyColor color.Color
	Stars           float64
	Accuracy        float64
	Days_ago        string
	UserMap_pp      float64
	IsBest30        bool
}

// PlayerData 代表玩家的整体数据
type PlayerData struct {
	PlayerName        string     // 玩家昵称
	PlayerID          string     // 玩家ID (可选)
	AvatarPath        string     // 玩家头像路径 (可选)
	PlayerCurrentPP   float64    // 玩家总 UserMap_pp
	B50Songs          []SongData // B50 歌曲列表 (通常是按 UserMap_pp 降序排列)
	PlayerRank        int        // 玩家排名
	PlayerCountryRank int        // 玩家国家排名
	Country           string     // 玩家所在国家
}

// ScoreSaber用户信息结构体
type ScoreSaberUser struct {
	ID             string  `json:"id"`
	Name           string  `json:"name"`
	ProfilePicture string  `json:"profilePicture"`
	Country        string  `json:"country"`
	PP             float64 `json:"pp"`
	Rank           int     `json:"rank"`
	CountryRank    int     `json:"countryRank"`
}

// ScoreSaber 相关结构体
type ScoreSaberScore struct {
	ID                    int64       `json:"id"`
	LeaderboardPlayerInfo interface{} `json:"leaderboardPlayerInfo"`
	Rank                  int         `json:"rank"`
	BaseScore             int         `json:"baseScore"`
	ModifiedScore         int         `json:"modifiedScore"`
	PP                    float64     `json:"pp"`
	Weight                float64     `json:"weight"`
	Modifiers             string      `json:"modifiers"`
	Multiplier            float64     `json:"multiplier"`
	BadCuts               int         `json:"badCuts"`
	MissedNotes           int         `json:"missedNotes"`
	MaxCombo              int         `json:"maxCombo"`
	FullCombo             bool        `json:"fullCombo"`
	HMD                   int         `json:"hmd"`
	TimeSet               string      `json:"timeSet"`
	HasReplay             bool        `json:"hasReplay"`
	DeviceHMD             string      `json:"deviceHmd"`
	DeviceControllerLeft  string      `json:"deviceControllerLeft"`
	DeviceControllerRight string      `json:"deviceControllerRight"`
}

type ScoreSaberLeaderboard struct {
	ID              int64  `json:"id"`
	SongHash        string `json:"songHash"`
	SongName        string `json:"songName"`
	SongSubName     string `json:"songSubName"`
	SongAuthorName  string `json:"songAuthorName"`
	LevelAuthorName string `json:"levelAuthorName"`
	Difficulty      struct {
		LeaderboardID int    `json:"leaderboardId"`
		Difficulty    int    `json:"difficulty"`
		GameMode      string `json:"gameMode"`
		DifficultyRaw string `json:"difficultyRaw"`
	} `json:"difficulty"`
	MaxScore          int         `json:"maxScore"`
	CreatedDate       string      `json:"createdDate"`
	RankedDate        string      `json:"rankedDate"`
	QualifiedDate     string      `json:"qualifiedDate"`
	LovedDate         interface{} `json:"lovedDate"`
	Ranked            bool        `json:"ranked"`
	Qualified         bool        `json:"qualified"`
	Loved             bool        `json:"loved"`
	MaxPP             int         `json:"maxPP"`
	Stars             float64     `json:"stars"`
	Plays             int         `json:"plays"`
	DailyPlays        int         `json:"dailyPlays"`
	PositiveModifiers bool        `json:"positiveModifiers"`
	PlayerScore       interface{} `json:"playerScore"`
	CoverImage        string      `json:"coverImage"`
	Difficulties      interface{} `json:"difficulties"`
}

type ScoreSaberPlayerScore struct {
	Score       ScoreSaberScore       `json:"score"`
	Leaderboard ScoreSaberLeaderboard `json:"leaderboard"`
}

type ScoreSaberResponse struct {
	PlayerScores []ScoreSaberPlayerScore `json:"playerScores"`
	Metadata     struct {
		Total        int `json:"total"`
		Page         int `json:"page"`
		ItemsPerPage int `json:"itemsPerPage"`
	} `json:"metadata"`
}

// loadFont 加载字体文件的辅助函数
func loadFont(path string) (*opentype.Font, error) {
	fontBytes, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	f, err := opentype.Parse(fontBytes)
	if err != nil {
		return nil, err
	}
	return f, nil
}

// generateB50Image 生成 B50 图片
func generateB50Image(player PlayerData, outputPath string, fontPath string) error {
	const (
		imgWidth          = 2000                                 // 增加画布宽度以适应四列
		padding           = 40.0                                 // 边距
		headerHeight      = 150.0                                // 头部区域高度
		songEntryHeight   = 100.0                                // 每首歌曲条目高度
		songEntryPadding  = 10.0                                 // 歌曲条目内边距
		jacketSize        = songEntryHeight - 2*songEntryPadding // 封面大小
		songsPerRow       = 4                                    // 改为4列
		maxSongsPerColumn = 10                                   // 每列最多显示10首歌曲
		columnGap         = 25.0                                 // 列之间的间距
	)
	var imgHeight int
	// 计算实际需要的画布高度
	totalRows := (len(player.B50Songs) + songsPerRow - 1) / songsPerRow
	calculatedHeight := float64(headerHeight + padding*2 + totalRows*(songEntryHeight+padding))
	if calculatedHeight > float64(imgHeight) {
		imgHeight = int(calculatedHeight) + 100 // 额外加一些空间
	}

	dc := gg.NewContext(imgWidth, imgHeight)
	mainFont, err := loadFont(fontPath)
	if err != nil {
		return fmt.Errorf("加载主字体失败: %w", err)
	}

	yOffset := padding * 1.5 // 增加顶部间距

	dc.SetRGBA(0, 0, 0, 0.5)
	dc.DrawRoundedRectangle(padding, yOffset, float64(imgWidth)-padding*2, headerHeight, 15)
	dc.Fill()

	// 玩家头像 (可选)
	avatarSpace := padding * 2 // 默认空间
	if player.AvatarPath != "" {
		avatarImg, err := loadImageFromCache(player.AvatarPath)
		if err == nil {
			avatarSize := headerHeight - padding
			dc.DrawImageAnchored(avatarImg, int(padding*2+avatarSize/2), int(yOffset+headerHeight/2), 0.5, 0.5)
			avatarSpace = padding*2 + avatarSize + padding
		} else {
			log.Printf("警告: 加载头像 %s 失败: %v", player.AvatarPath, err)
		}
	}

	// 玩家昵称
	faceTitle, err := opentype.NewFace(mainFont, &opentype.FaceOptions{
		Size: 54, // 增大字体
		DPI:  72,
	})
	if err != nil {
		return fmt.Errorf("创建标题字体失败: %w", err)
	}
	dc.SetFontFace(faceTitle)
	dc.SetHexColor("#FFFFFF")
	dc.DrawStringAnchored(player.PlayerName, avatarSpace, yOffset+headerHeight/4, 0.0, 0.5)

	faceCountry, err := opentype.NewFace(mainFont, &opentype.FaceOptions{
		Size: 30,
		DPI:  72,
	})
	if err != nil {
		return fmt.Errorf("创建国家信息字体失败: %w", err)
	}
	dc.SetFontFace(faceCountry)

	// 排名信息
	rankInfo := fmt.Sprintf("全球排名: #%d | [%s] 排名: #%d", player.PlayerRank, player.Country, player.PlayerCountryRank)
	dc.DrawStringAnchored(rankInfo, avatarSpace, yOffset+headerHeight*0.75, 0.0, 0.5)

	// 总 UserMap_pp
	faceUserMap_pp, err := opentype.NewFace(mainFont, &opentype.FaceOptions{
		Size: 42, // 增大字体
		DPI:  72,
	})
	if err != nil {
		return fmt.Errorf("创建评分字体失败: %w", err)
	}
	dc.SetFontFace(faceUserMap_pp)
	dc.SetHexColor("#FFD700") // 金色
	ppText := fmt.Sprintf("%.2f pp", player.PlayerCurrentPP)
	dc.DrawStringAnchored(ppText, imgWidth-padding, yOffset+headerHeight/2, 1.0, 0.5)

	// B30 / R10 平均
	faceSubtitle, err := opentype.NewFace(mainFont, &opentype.FaceOptions{
		Size: 28,
		DPI:  72,
	})
	if err != nil {
		return fmt.Errorf("创建副标题字体失败: %w", err)
	}
	dc.SetFontFace(faceSubtitle)
	dc.SetHexColor("#DDDDDD")

	yOffset += headerHeight + padding*1.5 // 增加间距

	// --- 5. 绘制列标题 ---
	dc.SetFontFace(faceSubtitle)
	dc.SetHexColor("#FFFFFF")

	// 计算每列的宽度和间距
	totalContentWidth := imgWidth - padding*2
	songEntryWidth := (totalContentWidth - columnGap*(float64(songsPerRow)-1)) / float64(songsPerRow)

	// 绘制标题栏
	sectionNames := []string{"Best 30 (1-10)", "Best 30 (11-20)", "Best 30 (21-30)", "Recent 10"}
	for i := 0; i < songsPerRow; i++ {
		sectionX := padding + float64(i)*(songEntryWidth+columnGap) + songEntryWidth/2
		dc.DrawStringAnchored(sectionNames[i], sectionX, yOffset, 0.5, 0.5)
	}

	yOffset += padding * 1.5

	// --- 6. 为每个区域添加半透明背景 ---
	for i := 0; i < songsPerRow; i++ {
		sectionX := padding + float64(i)*(songEntryWidth+columnGap)
		sectionHeight := float64(maxSongsPerColumn) * (songEntryHeight + padding)
		dc.SetRGBA(0, 0, 0, 0.3)
		dc.DrawRoundedRectangle(sectionX, yOffset, songEntryWidth, sectionHeight, 10)
		dc.Fill()
	}

	// --- 7. 绘制歌曲列表 ---
	faceSongTitle, err := opentype.NewFace(mainFont, &opentype.FaceOptions{Size: 24, DPI: 72})
	if err != nil {
		return fmt.Errorf("创建歌曲标题字体失败: %w", err)
	}
	faceSongDetail, err := opentype.NewFace(mainFont, &opentype.FaceOptions{Size: 20, DPI: 72})
	if err != nil {
		return fmt.Errorf("创建歌曲详情字体失败: %w", err)
	}
	faceSongUserMap_pp, err := opentype.NewFace(mainFont, &opentype.FaceOptions{Size: 28, DPI: 72})
	if err != nil {
		return fmt.Errorf("创建歌曲评分字体失败: %w", err)
	}
	// 计算每列的起始位置
	columnStartX := make([]float64, songsPerRow)
	for i := 0; i < songsPerRow; i++ {
		columnStartX[i] = padding + float64(i)*(songEntryWidth+columnGap)
	}
	for i, song := range player.B50Songs {
		if i >= 40 {
			continue
		}
		var col, row int
		if i < 30 {
			col = i / 10
			row = i % 10
		} else {
			col = 3
			row = i - 30
		}
		startX := columnStartX[col]
		startY := yOffset + float64(row)*(songEntryHeight+padding)
		dc.SetRGBA(1, 1, 1, 0.1)
		dc.DrawRoundedRectangle(startX+songEntryPadding/2, startY+songEntryPadding/2,
			songEntryWidth-songEntryPadding, songEntryHeight-songEntryPadding, 8)
		dc.Fill()
		jacketX := startX + songEntryPadding*2
		jacketY := startY + songEntryPadding*2
		if song.JacketPath != "" {
			jacketImg, err := loadImageFromCache(song.JacketPath)
			if err == nil {
				jacketImg = resizeImage(jacketImg, int(jacketSize), int(jacketSize))
				dc.DrawImageAnchored(jacketImg, int(jacketX+jacketSize/2), int(jacketY+jacketSize/2), 0.5, 0.5)
			} else {
				log.Printf("警告: 加载封面 %s 失败: %v. 绘制占位符.", song.JacketPath, err)
				dc.SetHexColor("#505050")
				dc.DrawRoundedRectangle(jacketX, jacketY, jacketSize, jacketSize, 5)
				dc.Fill()
				dc.SetHexColor("#FFFFFF")
				dc.SetFontFace(faceSongDetail)
				dc.DrawStringAnchored("No Art", jacketX+jacketSize/2, jacketY+jacketSize/2, 0.5, 0.5)
			}
		} else {
			dc.SetHexColor("#505050")
			dc.DrawRoundedRectangle(jacketX, jacketY, jacketSize, jacketSize, 5)
			dc.Fill()
			dc.SetHexColor("#FFFFFF")
			dc.SetFontFace(faceSongDetail)
			dc.DrawStringAnchored("No Art", jacketX+jacketSize/2, jacketY+jacketSize/2, 0.5, 0.5)
		}
		textStartX := jacketX + jacketSize + songEntryPadding
		textWidth := songEntryWidth - jacketSize - 4*songEntryPadding
		dc.SetFontFace(faceSongDetail)
		dc.SetHexColor("#AAAAAA")
		indexText := fmt.Sprintf("#%d", i+1)
		dc.DrawStringAnchored(indexText, startX+songEntryPadding, startY+songEntryPadding, 0, 0)
		dc.SetFontFace(faceSongTitle)
		dc.SetHexColor("#FFFFFF")
		titleToShow := song.Title
		if w, _ := dc.MeasureString(titleToShow); w > textWidth*0.85 {
			ellipsisWidth, _ := dc.MeasureString("...")
			for len(titleToShow) > 0 && w > textWidth*0.85-ellipsisWidth {
				titleToShow = titleToShow[:len(titleToShow)-1]
				w, _ = dc.MeasureString(titleToShow)
			}
			titleToShow += "..."
		}
		dc.DrawString(titleToShow, textStartX, startY+songEntryPadding*2+20)
		dc.SetFontFace(faceSongDetail)
		dc.SetHexColor("#BBBBBB")
		BeatmapidToShow := song.Beatmapid
		if w, _ := dc.MeasureString(BeatmapidToShow); w > textWidth*0.85 {
			ellipsisWidth, _ := dc.MeasureString("...")
			for len(BeatmapidToShow) > 0 && w > textWidth*0.85-ellipsisWidth {
				BeatmapidToShow = BeatmapidToShow[:len(BeatmapidToShow)-1]
				w, _ = dc.MeasureString(BeatmapidToShow)
			}
			BeatmapidToShow += "..."
		}
		dc.DrawString(BeatmapidToShow, textStartX, startY+songEntryPadding*2+50)
		dc.SetFontFace(faceSongDetail)
		if song.DifficultyColor != nil {
			dc.SetColor(song.DifficultyColor)
		} else {
			dc.SetHexColor("#FFFFFF")
		}
		diffText := fmt.Sprintf("[%s]", song.Difficulty)
		dc.DrawString(diffText, textStartX, startY+songEntryPadding*2+80)
		rightSideX := startX + songEntryWidth - songEntryPadding*2
		dc.SetFontFace(faceSongDetail)
		dc.SetHexColor("#FFCC22")
		starsText := fmt.Sprintf("%.2f★", song.Stars)
		dc.DrawStringAnchored(starsText, rightSideX, startY+songEntryHeight*0.25, 1.0, 0.5)
		dc.SetFontFace(faceSongUserMap_pp)
		if i < 30 {
			dc.SetHexColor("#FFEE77")
		} else {
			dc.SetHexColor("#ADD8E6")
		}
		dc.DrawStringAnchored(fmt.Sprintf("%.2fpp", song.UserMap_pp),
			rightSideX, startY+songEntryHeight*0.45, 1.0, 0.5)
		dc.SetFontFace(faceSongDetail)
		dc.SetHexColor("#AAAAAA")
		accText := fmt.Sprintf("Acc: %.2f%%", song.Accuracy)
		dc.DrawStringAnchored(accText, rightSideX, startY+songEntryHeight*0.75, 1.0, 0.5)
		daysText := song.Days_ago
		dc.DrawStringAnchored(daysText, rightSideX, startY+songEntryHeight*0.95, 1.0, 0.5)
	}

	dc.SetFontFace(faceSongDetail)
	dc.SetHexColor("#999999")
	generatedTime := time.Now().Format("2006-01-02 15:04:05")
	dc.DrawStringAnchored(fmt.Sprintf("Generated: %s", generatedTime), padding, float64(imgHeight)-padding, 0, 1.0)
	return dc.SavePNG(outputPath)
}

// 获取ScoreSaber用户信息
func fetchScoreSaberUser(userID string) (*ScoreSaberUser, error) {
	url := "https://scoresaber.com/api/player/" + userID + "/full"
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("HTTP状态码错误: %d", resp.StatusCode)
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	var user ScoreSaberUser
	err = json.Unmarshal(body, &user)
	if err != nil {
		return nil, err
	}
	return &user, nil
}

// 下载图片到本地临时文件，返回本地路径
func downloadImageToLocal(url string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	// 生成临时文件
	// tmpFile, err := os.CreateTemp("", "avatar_*.jpg")
	uuid := uuid.New().String()
	tmpFile, err := os.Create(util.GenTempFilePath(uuid + ".jpg"))
	if err != nil {
		return "", err
	}
	defer tmpFile.Close()

	_, err = io.Copy(tmpFile, resp.Body)
	if err != nil {
		return "", err
	}
	return tmpFile.Name(), nil
}

// 缩放图片到指定宽高
func resizeImage(img image.Image, width, height int) image.Image {
	return resize.Resize(uint(width), uint(height), img, resize.Lanczos3)
}

// getScoreSaberScores 获取玩家的 ScoreSaber 分数
func getScoreSaberScores(playerID string, sort string, page int) (*ScoreSaberResponse, error) {
	url := fmt.Sprintf("https://scoresaber.com/api/player/%s/scores?sort=%s&page=%d", playerID, sort, page)

	resp, err := http.Get(url)
	if err != nil {
		return nil, fmt.Errorf("请求 ScoreSaber API 失败: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("ScoreSaber API 返回错误状态码: %d", resp.StatusCode)
	}

	var result ScoreSaberResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("解析 ScoreSaber API 响应失败: %v", err)
	}

	return &result, nil
}

// 难度颜色映射
var difficultyColors = map[string]color.Color{
	"Easy":       color.RGBA{R: 0x00, G: 0xFF, B: 0x00, A: 0xFF}, // 绿色
	"Normal":     color.RGBA{R: 0x00, G: 0x7F, B: 0xFF, A: 0xFF}, // 蓝色
	"Hard":       color.RGBA{R: 0xFF, G: 0x7F, B: 0x00, A: 0xFF}, // 橙色
	"Expert":     color.RGBA{R: 0xFF, G: 0x00, B: 0x00, A: 0xFF}, // 红色
	"ExpertPlus": color.RGBA{R: 0xB4, G: 0x00, B: 0xB4, A: 0xFF}, // 紫色
}

// 将 ScoreSaber 分数转换为 SongData
func convertScoreSaberToSongData(score ScoreSaberPlayerScore, cwd string) (SongData, error) {
	// 下载封面图片
	jacketPath := util.GenTempFilePath(score.Leaderboard.SongHash + ".png")
	if _, err := os.Stat(jacketPath); os.IsNotExist(err) {
		resp, err := http.Get(score.Leaderboard.CoverImage)
		if err != nil {
			return SongData{}, fmt.Errorf("下载封面图片失败: %v", err)
		}
		defer resp.Body.Close()

		file, err := os.Create(jacketPath)
		if err != nil {
			return SongData{}, fmt.Errorf("创建封面文件失败: %v", err)
		}
		defer file.Close()

		_, err = io.Copy(file, resp.Body)
		if err != nil {
			return SongData{}, fmt.Errorf("保存封面图片失败: %v", err)
		}
	}

	// 获取难度名称
	difficultyName := "Unknown"
	switch score.Leaderboard.Difficulty.Difficulty {
	case 1:
		difficultyName = "Easy"
	case 3:
		difficultyName = "Normal"
	case 5:
		difficultyName = "Hard"
	case 7:
		difficultyName = "Expert"
	case 9:
		difficultyName = "ExpertPlus"
	}

	// 计算准确率
	accuracy := float64(score.Score.ModifiedScore) / float64(score.Leaderboard.MaxScore) * 100

	// 计算游玩时间
	timeSet, err := time.Parse(time.RFC3339, score.Score.TimeSet)
	if err != nil {
		return SongData{}, fmt.Errorf("解析时间失败: %v", err)
	}
	daysAgo := time.Since(timeSet)
	daysAgoStr := formatTimeAgo(daysAgo)

	return SongData{
		JacketPath:      jacketPath,
		Title:           score.Leaderboard.SongName,
		Beatmapid:       score.Leaderboard.SongAuthorName,
		Difficulty:      difficultyName,
		DifficultyColor: difficultyColors[difficultyName],
		Stars:           score.Leaderboard.Stars,
		Accuracy:        accuracy,
		Days_ago:        daysAgoStr,
		UserMap_pp:      score.Score.PP,
		IsBest30:        true, // 这个值需要根据排序来确定
	}, nil
}

// 格式化时间差
func formatTimeAgo(d time.Duration) string {
	days := int(d.Hours() / 24)
	if days > 0 {
		return fmt.Sprintf("%dd", days)
	}
	hours := int(d.Hours())
	if hours > 0 {
		return fmt.Sprintf("%dh", hours)
	}
	minutes := int(d.Minutes())
	if minutes > 0 {
		return fmt.Sprintf("%dm", minutes)
	}
	return "now"
}

// 获取Best30分数（最多30个）
func getBest30Scores(playerID string) ([]ScoreSaberPlayerScore, error) {
	var result []ScoreSaberPlayerScore
	page := 1
	for len(result) < 30 {
		resp, err := getScoreSaberScores(playerID, "top", page)
		if err != nil {
			return nil, err
		}
		if len(resp.PlayerScores) == 0 {
			break
		}
		result = append(result, resp.PlayerScores...)
		page++
	}
	if len(result) > 30 {
		result = result[:30]
	}
	return result, nil
}

// 获取Recent10分数（最多10个）
func getRecent10Scores(playerID string) ([]ScoreSaberPlayerScore, error) {
	var result []ScoreSaberPlayerScore
	page := 1
	for len(result) < 10 {
		resp, err := getScoreSaberScores(playerID, "recent", page)
		if err != nil {
			return nil, err
		}
		if len(resp.PlayerScores) == 0 {
			break
		}
		result = append(result, resp.PlayerScores...)
		page++
	}
	if len(result) > 10 {
		result = result[:10]
	}
	return result, nil
}

// 获取玩家的所有分数并转换为 SongData
func getAllScoreSaberScores(playerID string) ([]SongData, error) {
	var allSongs []SongData
	cwd, _ := os.Getwd()

	// 获取Best30
	topScores, err := getBest30Scores(playerID)
	if err != nil {
		return nil, fmt.Errorf("获取Best30失败: %v", err)
	}
	for i, score := range topScores {
		songData, err := convertScoreSaberToSongData(score, cwd)
		if err != nil {
			log.Printf("转换分数失败: %v", err)
			continue
		}
		songData.IsBest30 = i < 30
		allSongs = append(allSongs, songData)
	}

	// 获取Recent10
	recentScores, err := getRecent10Scores(playerID)
	if err != nil {
		return nil, fmt.Errorf("获取Recent10失败: %v", err)
	}
	for _, score := range recentScores {
		songData, err := convertScoreSaberToSongData(score, cwd)
		if err != nil {
			log.Printf("转换分数失败: %v", err)
			continue
		}
		songData.IsBest30 = false
		allSongs = append(allSongs, songData)
	}

	return allSongs, nil
}

func GenBs50(ssID string, outputPath string) error {
	userID := ssID
	ssUser, err := fetchScoreSaberUser(userID)
	if err != nil {
		log.Fatalf("获取ScoreSaber用户信息失败: %v", err)
	}
	songs, err := getAllScoreSaberScores(userID)
	if err != nil {
		log.Fatalf("获取分数失败: %v", err)
	}
	avatarPath := ssUser.ProfilePicture
	if strings.HasPrefix(avatarPath, "http") {
		localAvatar, err := downloadImageToLocal(avatarPath)
		if err == nil {
			avatarPath = localAvatar
		} else {
			log.Printf("下载头像失败: %v", err)
			avatarPath = ""
		}
	}
	player := PlayerData{
		PlayerName:        ssUser.Name,
		PlayerID:          ssUser.ID,
		AvatarPath:        avatarPath,
		PlayerCurrentPP:   ssUser.PP,
		PlayerRank:        ssUser.Rank,
		PlayerCountryRank: ssUser.CountryRank,
		B50Songs:          songs,
		Country:           ssUser.Country,
	}
	if err = generateB50Image(player, outputPath, "resource/font.ttf"); err != nil {
		return fmt.Errorf("生成 B50 图片失败: %v", err)
	}
	return nil
}
