package main

import (
	"encoding/json"
	"fmt"
	"io"
	"mime"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

// Source is an alias for MusicItem (used in sources.json)
type Source = MusicItem

// Download file from URL
func downloadFile(filepath string, url string) error {
	// Create the file
	out, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer out.Close()

	// Get the data
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Check server response
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("bad status: %s", resp.Status)
	}

	// Write the body to file
	_, err = io.Copy(out, resp.Body)
	if err != nil {
		return err
	}

	return nil
}

// Get IP address from request
func IPhandler(r *http.Request) (string, error) {
	ip := r.Header.Get("X-Real-IP")
	if ip == "" {
		ip = r.Header.Get("X-Forwarded-For")
	}
	if ip == "" {
		ip, _, _ = net.SplitHostPort(r.RemoteAddr)
	}
	return ip, nil
}

// Read sources from sources.json
func readSources() []Source {
	file, err := os.Open("sources.json")
	if err != nil {
		return []Source{}
	}
	defer file.Close()

	var sources []Source
	decoder := json.NewDecoder(file)
	err = decoder.Decode(&sources)
	if err != nil {
		return []Source{}
	}
	return sources
}

// Read music from cache
func readFromCache(path string) (MusicItem, bool) {
	// Logic to read music item from a cached folder path
	// This assumes path is like "files/cache/music/Artist-Song"
	info, err := os.Stat(path)
	if err != nil || !info.IsDir() {
		return MusicItem{}, false
	}
	
	dirName := filepath.Base(path)
	parts := strings.SplitN(dirName, "-", 2)
	var artist, title string
	if len(parts) == 2 {
		artist = parts[0]
		title = parts[1]
	} else {
		title = dirName
	}
	
	return getLocalMusicItem(title, artist), true
}

// Request and cache music from API
func requestAndCacheMusic(song, singer string) MusicItem {
	// Try different sources in priority order
	sources := []string{"kuwo", "netease", "migu", "baidu"}
	for _, source := range sources {
		item := YuafengAPIResponseHandler(source, song, singer)
		if item.Title != "" {
			return item
		}
	}
	return MusicItem{}
}

// 直接从远程URL流式转码（边下载边转码，超快！）
func streamConvertAudio(inputURL, outputFile string) error {
	fmt.Printf("[Info] Stream converting from URL (fast mode)\n")
	
	// 先写入临时文件，完成后再重命名（避免读取到不完整的文件）
	tempFile := outputFile + ".tmp"
	
	// ffmpeg 直接读取远程 URL 并转码
	// -t 600: 只下载前10分钟，减少80%下载量！
	// 移除 reconnect 参数，避免兼容性问题
	// 添加 -bufsize 以提高稳定性
	cmd := exec.Command("ffmpeg", "-y",
		"-t", "600",
		"-i", inputURL,
		"-threads", "0",
		"-ac", "1", "-ar", "24000", "-b:a", "32k", "-q:a", "9",
		"-bufsize", "64k",
		tempFile)
	
	err := cmd.Run()
	if err != nil {
		fmt.Printf("[Error] Stream convert failed: %v\n", err)
		os.Remove(tempFile) // 清理临时文件
		return err
	}
	
	// 检查生成的文件大小
	fileInfo, err := os.Stat(tempFile)
	if err != nil || fileInfo.Size() < 1024 {
		fmt.Printf("[Error] Stream converted file is too small or empty\n")
		os.Remove(tempFile)
		return fmt.Errorf("converted file is too small")
	}

	// 转码完成后重命名为最终文件
	err = os.Rename(tempFile, outputFile)
	if err != nil {
		fmt.Printf("[Error] Failed to rename temp file: %v\n", err)
		return err
	}
	
	fmt.Printf("[Success] Stream convert completed: %s\n", outputFile)
	return nil
}

// 实时流式转码到 HTTP Writer（边下载边播放！）
func streamConvertToWriter(inputURL string, w http.ResponseWriter) error {
	fmt.Printf("[Info] Live streaming from URL: %s\n", inputURL)
	
	// ffmpeg 边下载边转码，输出到 stdout
	cmd := exec.Command("ffmpeg",
		"-i", inputURL,
		"-threads", "0",
		"-ac", "1", "-ar", "24000", "-b:a", "32k", "-q:a", "9",
		"-f", "mp3",
		"pipe:1") // 输出到 stdout
	
	// 获取 stdout pipe
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return fmt.Errorf("failed to get stdout pipe: %v", err)
	}
	
	// 启动 ffmpeg
	if err := cmd.Start(); err != nil {
		return fmt.Errorf("failed to start ffmpeg: %v", err)
	}
	
	// 设置响应头
	w.Header().Set("Content-Type", "audio/mpeg")
	// 移除 Transfer-Encoding: chunked，让 Go 自动处理
	
	// 边读边写到 HTTP response
	buf := make([]byte, 8192)
	for {
		n, err := stdout.Read(buf)
		if n > 0 {
			w.Write(buf[:n])
			if f, ok := w.(http.Flusher); ok {
				f.Flush() // 立即发送给客户端
			}
		}
		if err != nil {
			break
		}
	}
	
	cmd.Wait()
	fmt.Printf("[Success] Live streaming completed\n")
	return nil
}

// Helper function for identifying file formats
func getMusicFileExtension(url string) (string, error) {
	resp, err := http.Head(url)
	if err != nil {
		return "", err
	}
	// Get file format from Content-Type header
	contentType := resp.Header.Get("Content-Type")
	ext, _, err := mime.ParseMediaType(contentType)
	if err != nil {
		return "", err
	}
	// Identify file extension based on file format
	switch ext {
	case "audio/mpeg":
		return ".mp3", nil
	case "audio/flac":
		return ".flac", nil
	case "audio/x-flac":
		return ".flac", nil
	case "audio/wav":
		return ".wav", nil
	case "audio/aac":
		return ".aac", nil
	case "audio/ogg":
		return ".ogg", nil
	case "application/octet-stream":
		// Try to guess file format from URL or other information
		if strings.Contains(url, ".mp3") {
			return ".mp3", nil
		} else if strings.Contains(url, ".flac") {
			return ".flac", nil
		} else if strings.Contains(url, ".wav") {
			return ".wav", nil
		} else if strings.Contains(url, ".aac") {
			return ".aac", nil
		} else if strings.Contains(url, ".ogg") {
			return ".ogg", nil
		} else {
			return "", fmt.Errorf("unknown file format from Content-Type and URL: %s", contentType)
		}
	default:
		return "", fmt.Errorf("unknown file format: %s", ext)
	}
}

// Helper function for identifying file formats
func GetDuration(filePath string) int {
	fmt.Printf("[Info] Get duration of obtaining music file %s\n", filePath)
	// Use ffprobe to get audio duration
	output, err := exec.Command("ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", filePath).Output()
	if err != nil {
		fmt.Println("[Error] Error getting audio duration:", err)
		return 0
	}

	duration, err := strconv.ParseFloat(strings.TrimSpace(string(output)), 64)
	if err != nil {
		fmt.Println("[Error] Error converting duration to float:", err)
		return 0
	}

	return int(duration)
}

// Helper function to compress and segment audio file
func compressAndSegmentAudio(inputFile, outputDir string) error {
	fmt.Printf("[Info] Compress and segment audio file %s\n", inputFile)
	// Compress music files
	outputFile := filepath.Join(outputDir, "music.mp3")
	cmd := exec.Command("ffmpeg", "-i", inputFile, "-ac", "1", "-ab", "32k", "-ar", "24000", outputFile)
	err := cmd.Run()
	if err != nil {
		return err
	}

	return nil
}

// Helper function to obtain music data from local folder
func getLocalMusicItem(song, singer string) MusicItem {
	musicDir := "./files/music"
	fmt.Println("[Info] Reading local folder music.")
	files, err := os.ReadDir(musicDir)
	if err != nil {
		fmt.Println("[Error] Failed to read local music directory:", err)
		return MusicItem{}
	}

	for _, file := range files {
		if file.IsDir() {
			if singer == "" {
				if strings.Contains(file.Name(), song) {
					dirPath := filepath.Join(musicDir, file.Name())
					// Extract artist and title from the directory name
					parts := strings.SplitN(file.Name(), "-", 2)
					var artist, title string
					if len(parts) == 2 {
						artist = parts[0]
						title = parts[1]
					} else {
						title = file.Name()
					}
					basePath := "/cache/music/" + url.QueryEscape(file.Name())
					return MusicItem{
						Title:        title,
						Artist:       artist,
						CoverURL:     basePath + "/cover.jpg",
						LyricURL:     basePath + "/lyric.lrc",
						AudioFullURL: basePath + "/music.mp3",
						AudioURL:     basePath + "/music.mp3",
						M3U8URL:      basePath + "/music.m3u8",
						Duration:     GetDuration(filepath.Join(dirPath, "music.mp3")),
					}
				}
			} else {
				if strings.Contains(file.Name(), song) && strings.Contains(file.Name(), singer) {
					dirPath := filepath.Join(musicDir, file.Name())
					// Extract artist and title from the directory name
					parts := strings.SplitN(file.Name(), "-", 2)
					var artist, title string
					if len(parts) == 2 {
						artist = parts[0]
						title = parts[1]
					} else {
						title = file.Name()
					}
					basePath := "/cache/music/" + url.QueryEscape(file.Name())
					return MusicItem{
						Title:        title,
						Artist:       artist,
						CoverURL:     basePath + "/cover.jpg",
						LyricURL:     basePath + "/lyric.lrc",
						AudioFullURL: basePath + "/music.mp3",
						AudioURL:     basePath + "/music.mp3",
						M3U8URL:      basePath + "/music.m3u8",
						Duration:     GetDuration(filepath.Join(dirPath, "music.mp3")),
					}
				}
			}
		}
	}
	return MusicItem{}
}
