package services

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"sort"
	"strings"

	"kvtube-go/models"
)

var ytDlpBinPath string

func init() {
	ytDlpBinPath = resolveYtDlpBinPath()
}

func resolveYtDlpBinPath() string {
	// Check if yt-dlp is in PATH
	if _, err := exec.LookPath("yt-dlp"); err == nil {
		return "yt-dlp"
	}

	fallbacks := []string{
		os.ExpandEnv("$HOME/Library/Python/3.14/bin/yt-dlp"),
		os.ExpandEnv("$HOME/Library/Python/3.13/bin/yt-dlp"),
		os.ExpandEnv("$HOME/Library/Python/3.12/bin/yt-dlp"),
		os.ExpandEnv("$HOME/Library/Python/3.11/bin/yt-dlp"),
		os.ExpandEnv("$HOME/.local/bin/yt-dlp"),
		"/usr/local/bin/yt-dlp",
		"/opt/homebrew/bin/yt-dlp",
	}

	for _, fb := range fallbacks {
		if _, err := os.Stat(fb); err == nil {
			return fb
		}
	}

	// Default fallback
	return "yt-dlp"
}

type VideoData struct {
	ID          string `json:"id"`
	Title       string `json:"title"`
	Uploader    string `json:"uploader"`
	ChannelID   string `json:"channel_id"`
	UploaderID  string `json:"uploader_id"`
	Thumbnail   string `json:"thumbnail"`
	ViewCount   int64  `json:"view_count"`
	UploadDate  string `json:"upload_date"`
	Duration    string `json:"duration"`
	Description string `json:"description"`
	StreamURL   string `json:"stream_url,omitempty"`
}

type VideoFormat struct {
	FormatID   string `json:"format_id"`
	FormatNote string `json:"format_note"`
	Ext        string `json:"ext"`
	Resolution string `json:"resolution"`
	Filesize   int64  `json:"filesize"`
	VCodec     string `json:"vcodec"`
	ACodec     string `json:"acodec"`
	Type       string `json:"type"` // "video", "audio", or "both"
}

type YtDlpEntry struct {
	ID          string      `json:"id"`
	Title       string      `json:"title"`
	Uploader    string      `json:"uploader"`
	Channel     string      `json:"channel"`
	ChannelID   string      `json:"channel_id"`
	UploaderID  string      `json:"uploader_id"`
	ViewCount   int64       `json:"view_count"`
	UploadDate  string      `json:"upload_date"`
	Duration    interface{} `json:"duration"` // Can be float64 or int
	Description string      `json:"description"`
	URL         string      `json:"url"`
}

func sanitizeVideoData(entry YtDlpEntry) VideoData {
	uploader := entry.Uploader
	if uploader == "" {
		uploader = entry.Channel
	}
	if uploader == "" {
		uploader = "Unknown"
	}

	var durationStr string
	if d, ok := entry.Duration.(float64); ok && d > 0 {
		hours := int(d) / 3600
		mins := (int(d) % 3600) / 60
		secs := int(d) % 60
		if hours > 0 {
			durationStr = fmt.Sprintf("%d:%02d:%02d", hours, mins, secs)
		} else {
			durationStr = fmt.Sprintf("%d:%02d", mins, secs)
		}
	}

	thumbnail := ""
	if entry.ID != "" {
		thumbnail = fmt.Sprintf("https://i.ytimg.com/vi/%s/hqdefault.jpg", entry.ID)
	}

	return VideoData{
		ID:          entry.ID,
		Title:       entry.Title,
		Uploader:    uploader,
		ChannelID:   entry.ChannelID,
		UploaderID:  entry.UploaderID,
		Thumbnail:   thumbnail,
		ViewCount:   entry.ViewCount,
		UploadDate:  entry.UploadDate,
		Duration:    durationStr,
		Description: entry.Description,
	}
}

// extractVideoID tries to extract a YouTube video ID from yt-dlp arguments
func extractVideoID(args []string) string {
	for _, arg := range args {
		// Look for 11-character video IDs (YouTube standard)
		if len(arg) == 11 {
			// Simple check: alphanumeric with underscore and dash
			isValid := true
			for _, c := range arg {
				if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-') {
					isValid = false
					break
				}
			}
			if isValid {
				return arg
			}
		}

		// Extract from YouTube URL patterns
		if strings.Contains(arg, "youtube.com") || strings.Contains(arg, "youtu.be") {
			// Simple regex for video ID in URL
			if idx := strings.Index(arg, "v="); idx != -1 {
				id := arg[idx+2:]
				if len(id) >= 11 {
					return id[:11]
				}
			}
			// youtu.be/ID
			if idx := strings.LastIndex(arg, "/"); idx != -1 {
				id := arg[idx+1:]
				if len(id) >= 11 {
					return id[:11]
				}
			}
		}
	}
	return ""
}

// RunYtDlpCached executes yt-dlp with caching
func RunYtDlpCached(cacheKey string, ttlSeconds int, args ...string) ([]byte, error) {
	// Try to get from cache first
	if cachedData, err := models.GetCachedVideo(cacheKey); err == nil && cachedData != nil {
		return cachedData, nil
	}

	// Execute yt-dlp
	data, err := RunYtDlp(args...)
	if err != nil {
		return nil, err
	}

	// Store in cache (ignore cache errors)
	if cacheKey != "" {
		_ = models.SetCachedVideo(cacheKey, string(data), ttlSeconds)
	}

	return data, nil
}

// RunYtDlp securely executes yt-dlp with the given arguments and returns JSON output
func RunYtDlp(args ...string) ([]byte, error) {
	cmdArgs := append([]string{
		"--dump-json",
		"--no-warnings",
		"--quiet",
		"--force-ipv4",
		"--ignore-errors",
		"--user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
	}, args...)

	cmd := exec.Command(ytDlpBinPath, cmdArgs...)

	var out bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &stderr

	err := cmd.Run()
	if err != nil {
		log.Printf("yt-dlp error: %v, stderr: %s", err, stderr.String())
		return nil, err
	}

	return out.Bytes(), nil
}

func SearchVideos(query string, limit int) ([]VideoData, error) {
	searchQuery := fmt.Sprintf("ytsearch%d:%s", limit, query)

	args := []string{
		"--flat-playlist",
		searchQuery,
	}

	out, err := RunYtDlp(args...)
	if err != nil {
		return nil, err
	}

	var results []VideoData
	lines := strings.Split(strings.TrimSpace(string(out)), "\n")
	for _, line := range lines {
		if line == "" {
			continue
		}
		var entry YtDlpEntry
		if err := json.Unmarshal([]byte(line), &entry); err == nil {
			if entry.ID != "" {
				results = append(results, sanitizeVideoData(entry))
			}
		}
	}

	return results, nil
}

func GetVideoInfo(videoID string) (*VideoData, error) {
	url := fmt.Sprintf("https://www.youtube.com/watch?v=%s", videoID)

	args := []string{
		"--format", "bestvideo+bestaudio/best",
		"--skip-download",
		"--no-playlist",
		url,
	}

	cacheKey := "video_info:" + videoID
	out, err := RunYtDlpCached(cacheKey, 3600, args...) // Cache for 1 hour
	if err != nil {
		return nil, err
	}

	var entry YtDlpEntry
	if err := json.Unmarshal(out, &entry); err != nil {
		return nil, err
	}

	data := sanitizeVideoData(entry)
	data.StreamURL = entry.URL

	return &data, nil
}

type QualityFormat struct {
	FormatID   string `json:"format_id"`
	Label      string `json:"label"`
	Resolution string `json:"resolution"`
	Height     int    `json:"height"`
	URL        string `json:"url"`
	AudioURL   string `json:"audio_url,omitempty"`
	IsHLS      bool   `json:"is_hls"`
	VCodec     string `json:"vcodec"`
	ACodec     string `json:"acodec"`
	Filesize   int64  `json:"filesize"`
	HasAudio   bool   `json:"has_audio"`
}

func GetVideoQualities(videoID string) ([]QualityFormat, error) {
	url := fmt.Sprintf("https://www.youtube.com/watch?v=%s", videoID)

	cmdArgs := []string{
		"--dump-json",
		"--no-warnings",
		"--quiet",
		"--force-ipv4",
		"--no-playlist",
		"--user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
		url,
	}

	cacheKey := "video_qualities:" + videoID
	out, err := RunYtDlpCached(cacheKey, 3600, cmdArgs...) // Cache for 1 hour
	if err != nil {
		return nil, err
	}

	var raw struct {
		Formats []struct {
			FormatID    string      `json:"format_id"`
			FormatNote  string      `json:"format_note"`
			Ext         string      `json:"ext"`
			Resolution  string      `json:"resolution"`
			Width       interface{} `json:"width"`
			Height      interface{} `json:"height"`
			URL         string      `json:"url"`
			ManifestURL string      `json:"manifest_url"`
			VCodec      string      `json:"vcodec"`
			ACodec      string      `json:"acodec"`
			Filesize    interface{} `json:"filesize"`
		} `json:"formats"`
	}

	if err := json.Unmarshal(out, &raw); err != nil {
		return nil, err
	}

	var qualities []QualityFormat
	seen := make(map[int]int) // height -> index in qualities

	for _, f := range raw.Formats {
		if f.VCodec == "none" || f.URL == "" {
			continue
		}

		var height int
		switch v := f.Height.(type) {
		case float64:
			height = int(v)
		case int:
			height = v
		}

		if height == 0 {
			continue
		}

		hasAudio := f.ACodec != "none" && f.ACodec != ""

		var filesize int64
		switch v := f.Filesize.(type) {
		case float64:
			filesize = int64(v)
		case int64:
			filesize = v
		}

		isHLS := f.ManifestURL != "" || strings.Contains(f.URL, ".m3u8") || strings.Contains(f.URL, "manifest")

		label := f.FormatNote
		if label == "" {
			switch height {
			case 2160:
				label = "4K"
			case 1440:
				label = "1440p"
			case 1080:
				label = "1080p"
			case 720:
				label = "720p"
			case 480:
				label = "480p"
			case 360:
				label = "360p"
			default:
				label = fmt.Sprintf("%dp", height)
			}
		}

		streamURL := f.URL
		if f.ManifestURL != "" {
			streamURL = f.ManifestURL
		}

		qf := QualityFormat{
			FormatID:   f.FormatID,
			Label:      label,
			Resolution: f.Resolution,
			Height:     height,
			URL:        streamURL,
			IsHLS:      isHLS,
			VCodec:     f.VCodec,
			ACodec:     f.ACodec,
			Filesize:   filesize,
			HasAudio:   hasAudio,
		}

		// Prefer formats with audio, otherwise just add
		if idx, exists := seen[height]; exists {
			// Replace if this one has audio and the existing one doesn't
			if hasAudio && !qualities[idx].HasAudio {
				qualities[idx] = qf
			}
		} else {
			seen[height] = len(qualities)
			qualities = append(qualities, qf)
		}
	}

	// Sort by height descending
	sort.Slice(qualities, func(i, j int) bool {
		return qualities[i].Height > qualities[j].Height
	})

	return qualities, nil
}

func GetBestAudioURL(videoID string) (string, error) {
	url := fmt.Sprintf("https://www.youtube.com/watch?v=%s", videoID)

	cmdArgs := []string{
		"--dump-json",
		"--no-warnings",
		"--quiet",
		"--force-ipv4",
		"--no-playlist",
		"--user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
		url,
	}

	binPath := "yt-dlp"
	if _, err := exec.LookPath("yt-dlp"); err != nil {
		fallbacks := []string{
			os.ExpandEnv("$HOME/.local/bin/yt-dlp"),
			"/usr/local/bin/yt-dlp",
			"/opt/homebrew/bin/yt-dlp",
			"/config/.local/bin/yt-dlp",
		}
		for _, fb := range fallbacks {
			if _, err := os.Stat(fb); err == nil {
				binPath = fb
				break
			}
		}
	}

	cmd := exec.Command(binPath, cmdArgs...)

	var out bytes.Buffer
	cmd.Stdout = &out

	if err := cmd.Run(); err != nil {
		return "", err
	}

	var raw struct {
		Formats []struct {
			FormatID   string      `json:"format_id"`
			URL        string      `json:"url"`
			VCodec     string      `json:"vcodec"`
			ACodec     string      `json:"acodec"`
			ABR        interface{} `json:"abr"`
			FormatNote string      `json:"format_note"`
		} `json:"formats"`
	}

	if err := json.Unmarshal(out.Bytes(), &raw); err != nil {
		return "", err
	}

	// Find best audio-only stream (prefer highest ABR)
	var bestAudio string
	var bestABR float64
	for _, f := range raw.Formats {
		if f.VCodec == "none" && f.ACodec != "none" && f.URL != "" {
			var abr float64
			switch v := f.ABR.(type) {
			case float64:
				abr = v
			case int:
				abr = float64(v)
			}
			if abr > bestABR {
				bestABR = abr
				bestAudio = f.URL
			}
		}
	}

	return bestAudio, nil
}

func GetVideoQualitiesWithAudio(videoID string) ([]QualityFormat, string, error) {
	qualities, err := GetVideoQualities(videoID)
	if err != nil {
		return nil, "", err
	}

	// Get best audio URL
	audioURL, err := GetBestAudioURL(videoID)
	if err != nil {
		log.Printf("Warning: could not get audio URL: %v", err)
	}

	// Attach audio URL to qualities without audio
	for i := range qualities {
		if !qualities[i].HasAudio && audioURL != "" {
			qualities[i].AudioURL = audioURL
		}
	}

	return qualities, audioURL, nil
}

// GetFullStreamData runs a single yt-dlp command to fetch all essential information at once
// This avoids doing 3 separate slow calls for video info, qualities, and best audio.
func GetFullStreamData(videoID string) (*VideoData, []QualityFormat, string, error) {
	urlStr := fmt.Sprintf("https://www.youtube.com/watch?v=%s", videoID)

	cmdArgs := []string{
		"--dump-json",
		"--no-warnings",
		"--quiet",
		"--force-ipv4",
		"--no-playlist",
		"--user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
		urlStr,
	}

	binPath := "yt-dlp"
	if _, err := exec.LookPath("yt-dlp"); err != nil {
		fallbacks := []string{
			os.ExpandEnv("$HOME/Library/Python/3.14/bin/yt-dlp"),
			os.ExpandEnv("$HOME/Library/Python/3.13/bin/yt-dlp"),
			os.ExpandEnv("$HOME/Library/Python/3.12/bin/yt-dlp"),
			os.ExpandEnv("$HOME/.local/bin/yt-dlp"),
			"/usr/local/bin/yt-dlp",
			"/opt/homebrew/bin/yt-dlp",
			"/config/.local/bin/yt-dlp",
		}
		for _, fb := range fallbacks {
			if _, err := os.Stat(fb); err == nil {
				binPath = fb
				break
			}
		}
	}

	cmd := exec.Command(binPath, cmdArgs...)

	var out bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		log.Printf("yt-dlp error in GetFullStreamData: %v, stderr: %s", err, stderr.String())
		return nil, nil, "", err
	}

	// Unmarshal common metadata
	var entry YtDlpEntry
	if err := json.Unmarshal(out.Bytes(), &entry); err != nil {
		return nil, nil, "", err
	}

	videoData := sanitizeVideoData(entry)
	videoData.StreamURL = entry.URL

	// Unmarshal formats specifically
	var raw struct {
		Formats []struct {
			FormatID    string      `json:"format_id"`
			FormatNote  string      `json:"format_note"`
			Ext         string      `json:"ext"`
			Resolution  string      `json:"resolution"`
			Width       interface{} `json:"width"`
			Height      interface{} `json:"height"`
			URL         string      `json:"url"`
			ManifestURL string      `json:"manifest_url"`
			VCodec      string      `json:"vcodec"`
			ACodec      string      `json:"acodec"`
			Filesize    interface{} `json:"filesize"`
			ABR         interface{} `json:"abr"`
		} `json:"formats"`
	}

	if err := json.Unmarshal(out.Bytes(), &raw); err != nil {
		return nil, nil, "", err
	}

	var qualities []QualityFormat
	seen := make(map[int]int) // height -> index in qualities
	var bestAudio string
	var bestABR float64

	for _, f := range raw.Formats {
		// Determine if it's the best audio
		if f.VCodec == "none" && f.ACodec != "none" && f.URL != "" {
			var abr float64
			switch v := f.ABR.(type) {
			case float64:
				abr = v
			case int:
				abr = float64(v)
			}
			if bestAudio == "" || abr > bestABR {
				bestABR = abr
				bestAudio = f.URL
			}
		}

		if f.VCodec == "none" || f.URL == "" {
			continue
		}

		var height int
		switch v := f.Height.(type) {
		case float64:
			height = int(v)
		case int:
			height = v
		}

		if height == 0 {
			continue
		}

		hasAudio := f.ACodec != "none" && f.ACodec != ""

		var filesize int64
		switch v := f.Filesize.(type) {
		case float64:
			filesize = int64(v)
		case int64:
			filesize = v
		}

		isHLS := f.ManifestURL != "" || strings.Contains(f.URL, ".m3u8") || strings.Contains(f.URL, "manifest")

		label := f.FormatNote
		if label == "" {
			switch height {
			case 2160:
				label = "4K"
			case 1440:
				label = "1440p"
			case 1080:
				label = "1080p"
			case 720:
				label = "720p"
			case 480:
				label = "480p"
			case 360:
				label = "360p"
			default:
				label = fmt.Sprintf("%dp", height)
			}
		}

		streamURL := f.URL
		if f.ManifestURL != "" {
			streamURL = f.ManifestURL
		}

		qf := QualityFormat{
			FormatID:   f.FormatID,
			Label:      label,
			Resolution: f.Resolution,
			Height:     height,
			URL:        streamURL,
			IsHLS:      isHLS,
			VCodec:     f.VCodec,
			ACodec:     f.ACodec,
			Filesize:   filesize,
			HasAudio:   hasAudio,
		}

		// Prefer formats with audio, otherwise just add
		if idx, exists := seen[height]; exists {
			// Replace if this one has audio and the existing one doesn't
			if hasAudio && !qualities[idx].HasAudio {
				qualities[idx] = qf
			}
		} else {
			seen[height] = len(qualities)
			qualities = append(qualities, qf)
		}
	}

	// Sort by height descending
	for i := range qualities {
		for j := i + 1; j < len(qualities); j++ {
			if qualities[j].Height > qualities[i].Height {
				qualities[i], qualities[j] = qualities[j], qualities[i]
			}
		}
	}

	// Attach audio URL to qualities without audio
	for i := range qualities {
		if !qualities[i].HasAudio && bestAudio != "" {
			qualities[i].AudioURL = bestAudio
		}
	}

	return &videoData, qualities, bestAudio, nil
}

func GetStreamURLForQuality(videoID string, height int) (string, error) {
	qualities, err := GetVideoQualities(videoID)
	if err != nil {
		return "", err
	}

	for _, q := range qualities {
		if q.Height == height {
			return q.URL, nil
		}
	}

	if len(qualities) > 0 {
		return qualities[0].URL, nil
	}

	return "", fmt.Errorf("no suitable quality found")
}

func GetRelatedVideos(title, uploader string, limit int) ([]VideoData, error) {
	query := title
	if uploader != "" {
		query = uploader + " " + title
	}
	// Limit query length to avoid issues
	if len(query) > 100 {
		query = query[:100]
	}
	return SearchVideos(query, limit)
}

type DownloadInfo struct {
	URL   string `json:"url"`
	Title string `json:"title"`
	Ext   string `json:"ext"`
}

func GetDownloadURL(videoID string, formatID string) (*DownloadInfo, error) {
	url := fmt.Sprintf("https://www.youtube.com/watch?v=%s", videoID)

	formatArgs := "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"
	if formatID != "" {
		formatArgs = formatID
		if !strings.Contains(formatID, "+") && !strings.Contains(formatID, "best") {
			// If it's just a video format, we might want to try adding audio, but for simple direct download links,
			// let's stick to what the user requested or what yt-dlp gives for that ID.
			formatArgs = formatID + "+bestaudio/best"
		}
	}

	args := []string{
		"--format", formatArgs,
		"--no-playlist",
		url,
	}

	out, err := RunYtDlp(args...)
	if err != nil {
		return nil, err
	}

	var raw map[string]interface{}
	if err := json.Unmarshal(out, &raw); err != nil {
		return nil, err
	}

	downloadURL, _ := raw["url"].(string)
	title, _ := raw["title"].(string)
	ext, _ := raw["ext"].(string)

	if downloadURL == "" {
		formats, ok := raw["formats"].([]interface{})
		if ok && len(formats) > 0 {
			// Try to find the first mp4 format that is not m3u8
			for i := len(formats) - 1; i >= 0; i-- {
				fmtMap, ok := formats[i].(map[string]interface{})
				if !ok {
					continue
				}
				fUrl, _ := fmtMap["url"].(string)
				fExt, _ := fmtMap["ext"].(string)
				if fUrl != "" && !strings.Contains(fUrl, ".m3u8") && fExt == "mp4" {
					downloadURL = fUrl
					ext = fExt
					break
				}
			}
		}
	}

	if title == "" {
		title = "video"
	}
	if ext == "" {
		ext = "mp4"
	}

	return &DownloadInfo{
		URL:   downloadURL,
		Title: title,
		Ext:   ext,
	}, nil
}

func GetVideoFormats(videoID string) ([]VideoFormat, error) {
	url := fmt.Sprintf("https://www.youtube.com/watch?v=%s", videoID)

	args := []string{
		"--dump-json",
		"--no-playlist",
		url,
	}

	out, err := RunYtDlp(args...)
	if err != nil {
		return nil, err
	}

	var raw struct {
		Formats []struct {
			FormatID   string  `json:"format_id"`
			FormatNote string  `json:"format_note"`
			Ext        string  `json:"ext"`
			Resolution string  `json:"resolution"`
			Filesize   float64 `json:"filesize"`
			VCodec     string  `json:"vcodec"`
			ACodec     string  `json:"acodec"`
		} `json:"formats"`
	}

	if err := json.Unmarshal(out, &raw); err != nil {
		return nil, err
	}

	var formats []VideoFormat
	for _, f := range raw.Formats {
		// Filter out storyboards and other non-media formats
		if strings.Contains(f.FormatID, "sb") || f.VCodec == "none" && f.ACodec == "none" {
			continue
		}

		fType := "both"
		if f.VCodec == "none" {
			fType = "audio"
		} else if f.ACodec == "none" {
			fType = "video"
		}

		formats = append(formats, VideoFormat{
			FormatID:   f.FormatID,
			FormatNote: f.FormatNote,
			Ext:        f.Ext,
			Resolution: f.Resolution,
			Filesize:   int64(f.Filesize),
			VCodec:     f.VCodec,
			ACodec:     f.ACodec,
			Type:       fType,
		})
	}

	return formats, nil
}

type ChannelInfo struct {
	ID              string `json:"id"`
	Title           string `json:"title"`
	SubscriberCount int64  `json:"subscriber_count"`
	Avatar          string `json:"avatar"`
}

func GetChannelInfo(channelID string) (*ChannelInfo, error) {
	url := fmt.Sprintf("https://www.youtube.com/channel/%s", channelID)
	if strings.HasPrefix(channelID, "@") {
		url = fmt.Sprintf("https://www.youtube.com/%s", channelID)
	}

	// Fetch 1 video with full metadata to extract channel info
	args := []string{
		url + "/videos",
		"--dump-json",
		"--playlist-end", "1",
		"--no-warnings",
		"--quiet",
	}

	out, err := RunYtDlp(args...)
	if err != nil || len(out) == 0 {
		return nil, fmt.Errorf("failed to get channel info: %v", err)
	}

	// Parse the first video's JSON
	var raw map[string]interface{}
	lines := strings.Split(strings.TrimSpace(string(out)), "\n")
	if len(lines) == 0 {
		return nil, fmt.Errorf("no output from yt-dlp")
	}

	if err := json.Unmarshal(out, &raw); err != nil {
		return nil, err
	}

	title, _ := raw["channel"].(string)
	if title == "" {
		title, _ = raw["uploader"].(string)
	}
	if title == "" {
		title = channelID
	}

	cID, _ := raw["channel_id"].(string)
	if cID == "" {
		cID = channelID
	}

	subCountFloat, _ := raw["channel_follower_count"].(float64)

	// Create an avatar based on the first letter of the channel title
	avatarStr := "?"
	if len(title) > 0 {
		avatarStr = strings.ToUpper(string(title[0]))
	}

	return &ChannelInfo{
		ID:              cID,
		Title:           title,
		SubscriberCount: int64(subCountFloat),
		Avatar:          avatarStr, // Simple fallback for now
	}, nil
}

func GetChannelVideos(channelID string, limit int) ([]VideoData, error) {
	url := fmt.Sprintf("https://www.youtube.com/channel/%s", channelID)
	if strings.HasPrefix(channelID, "@") {
		url = fmt.Sprintf("https://www.youtube.com/%s", channelID)
	}

	args := []string{
		url + "/videos",
		"--flat-playlist",
		fmt.Sprintf("--playlist-end=%d", limit),
	}

	out, err := RunYtDlp(args...)
	if err != nil {
		return nil, err
	}

	var results []VideoData
	lines := strings.Split(strings.TrimSpace(string(out)), "\n")
	for _, line := range lines {
		if line == "" {
			continue
		}
		var entry YtDlpEntry
		if err := json.Unmarshal([]byte(line), &entry); err == nil {
			if entry.ID != "" {
				results = append(results, sanitizeVideoData(entry))
			}
		}
	}

	return results, nil
}

type Comment struct {
	ID          string `json:"id"`
	Text        string `json:"text"`
	Author      string `json:"author"`
	AuthorID    string `json:"author_id"`
	AuthorThumb string `json:"author_thumbnail"`
	Likes       int    `json:"likes"`
	IsReply     bool   `json:"is_reply"`
	Parent      string `json:"parent"`
	Timestamp   string `json:"timestamp"`
}

func GetComments(videoID string, limit int) ([]Comment, error) {
	url := fmt.Sprintf("https://www.youtube.com/watch?v=%s", videoID)

	cmdArgs := []string{
		"--no-warnings",
		"--quiet",
		"--user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
		"--dump-json",
		"--no-download",
		"--no-playlist",
		"--write-comments",
		"--extractor-args", fmt.Sprintf("youtube:comment_sort=top;max_comments=%d", limit),
		url,
	}

	cmd := exec.Command(ytDlpBinPath, cmdArgs...)

	var out bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &stderr

	err := cmd.Run()
	if err != nil {
		log.Printf("yt-dlp comments error: %v, stderr: %s", err, stderr.String())
		return nil, err
	}

	var raw struct {
		Comments []struct {
			ID          string `json:"id"`
			Text        string `json:"text"`
			Author      string `json:"author"`
			AuthorID    string `json:"author_id"`
			AuthorThumb string `json:"author_thumbnail"`
			Likes       int    `json:"like_count"`
			IsReply     bool   `json:"is_reply"`
			Parent      string `json:"parent"`
			Timestamp   int64  `json:"timestamp"`
		} `json:"comments"`
	}

	if err := json.Unmarshal(out.Bytes(), &raw); err != nil {
		return nil, err
	}

	var comments []Comment
	for _, c := range raw.Comments {
		timestamp := ""
		if c.Timestamp > 0 {
			timestamp = formatCommentTime(c.Timestamp)
		}
		comments = append(comments, Comment{
			ID:          c.ID,
			Text:        c.Text,
			Author:      c.Author,
			AuthorID:    c.AuthorID,
			AuthorThumb: c.AuthorThumb,
			Likes:       c.Likes,
			IsReply:     c.IsReply,
			Parent:      c.Parent,
			Timestamp:   timestamp,
		})
	}

	return comments, nil
}

func formatCommentTime(timestamp int64) string {
	now := float64(timestamp)
	then := float64(0)
	diff := int((now - then) / 1000)

	if diff < 60 {
		return "just now"
	} else if diff < 3600 {
		return fmt.Sprintf("%dm ago", diff/60)
	} else if diff < 86400 {
		return fmt.Sprintf("%dh ago", diff/3600)
	} else if diff < 604800 {
		return fmt.Sprintf("%dd ago", diff/86400)
	} else if diff < 2592000 {
		return fmt.Sprintf("%dw ago", diff/604800)
	} else if diff < 31536000 {
		return fmt.Sprintf("%dmo ago", diff/2592000)
	}
	return fmt.Sprintf("%dy ago", diff/31536000)
}
