package util

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"sync"

	"github.com/tencentyun/cos-go-sdk-v5"
)

// COSConfig COS配置信息
type COSConfig struct {
	BucketURL string `mapstructure:"bucket_url"`
	SecretID  string `mapstructure:"secret_id"`
	SecretKey string `mapstructure:"secret_key"`
	BasePath  string `mapstructure:"base_path"`
}

// COSUtil COS工具类
type COSUtil struct {
	client   *cos.Client
	basePath string
}

var (
	cosUtilInstance *COSUtil
	once            sync.Once
)

// GetCOSUtil 获取COS工具类单例实例
func GetCOSUtil(config COSConfig) (*COSUtil, error) {
	var err error
	once.Do(func() {
		cosUtilInstance, err = newCOSUtil(config)
	})
	return cosUtilInstance, err
}

// newCOSUtil 创建新的COS工具类实例
func newCOSUtil(config COSConfig) (*COSUtil, error) {
	// 解析COS Bucket URL
	bucketURL, err := url.Parse(config.BucketURL)
	if err != nil {
		return nil, fmt.Errorf("解析COS Bucket URL失败: %v", err)
	}

	// 创建COS客户端
	client := cos.NewClient(&cos.BaseURL{BucketURL: bucketURL}, &http.Client{
		Transport: &cos.AuthorizationTransport{
			SecretID:  config.SecretID,
			SecretKey: config.SecretKey,
		},
	})

	return &COSUtil{
		client:   client,
		basePath: config.BasePath,
	}, nil
}

// UploadFile 上传单个文件到COS
func (c *COSUtil) UploadFile(localFilePath, cosPath string) error {
	// 如果cosPath为空，则使用文件名
	if cosPath == "" {
		cosPath = filepath.Base(localFilePath)
	}

	// 添加基础路径前缀
	if c.basePath != "" {
		cosPath = filepath.Join(c.basePath, cosPath)
	}

	// 规范化路径分隔符
	cosPath = filepath.ToSlash(cosPath)

	// 打开本地文件
	file, err := os.Open(localFilePath)
	if err != nil {
		return fmt.Errorf("打开本地文件失败: %v", err)
	}
	defer file.Close()

	// 上传文件到COS
	_, err = c.client.Object.Put(context.Background(), cosPath, file, nil)
	if err != nil {
		return fmt.Errorf("上传文件到COS失败: %v", err)
	}

	return nil
}

// BatchUpload 批量上传文件到COS（并发上传）
func (c *COSUtil) BatchUpload(fileMap map[string]string) error {
	// 使用goroutine并发上传文件
	var wg sync.WaitGroup
	errChan := make(chan error, len(fileMap))

	// 遍历文件映射进行上传
	for localPath, cosPath := range fileMap {
		wg.Add(1)
		go func(localPath, cosPath string) {
			defer wg.Done()
			if err := c.UploadFile(localPath, cosPath); err != nil {
				errChan <- fmt.Errorf("上传文件 %s 失败: %v", localPath, err)
			}
		}(localPath, cosPath)
	}

	// 等待所有上传完成
	wg.Wait()
	close(errChan)

	// 检查是否有错误
	for err := range errChan {
		if err != nil {
			return err
		}
	}

	return nil
}

// UploadDirectory 上传整个目录到COS（并发上传）
func (c *COSUtil) UploadDirectory(localDir, cosDir string) error {
	fileMap := make(map[string]string)

	// 遍历目录中的所有文件
	err := filepath.Walk(localDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// 只处理文件，跳过目录
		if !info.IsDir() {
			// 计算相对于本地目录的路径
			relPath, err := filepath.Rel(localDir, path)
			if err != nil {
				return fmt.Errorf("计算相对路径失败: %v", err)
			}

			// 构造COS路径
			cosPath := filepath.Join(cosDir, relPath)
			fileMap[path] = cosPath
		}

		return nil
	})

	if err != nil {
		return fmt.Errorf("遍历目录失败: %v", err)
	}

	// 批量上传文件（并发）
	return c.BatchUpload(fileMap)
}

// ConcurrentUploadDirectory 并发上传整个目录到COS（使用COS SDK的并发上传功能）
func (c *COSUtil) ConcurrentUploadDirectory(localDir, cosDir string, workers int) error {
	// 创建一个带缓冲的通道来控制并发数
	semaphore := make(chan struct{}, workers)

	// 使用WaitGroup等待所有goroutine完成
	var wg sync.WaitGroup
	errChan := make(chan error, 1) // 用于传递错误

	// 遍历目录中的所有文件
	err := filepath.Walk(localDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// 只处理文件，跳过目录
		if !info.IsDir() {
			// 计算相对于本地目录的路径
			relPath, err := filepath.Rel(localDir, path)
			if err != nil {
				return fmt.Errorf("计算相对路径失败: %v", err)
			}

			// 构造COS路径
			cosPath := filepath.Join(cosDir, relPath)

			// 启动goroutine并发上传
			wg.Add(1)
			go func(localPath, cosPath string) {
				defer wg.Done()

				// 获取信号量
				semaphore <- struct{}{}
				defer func() { <-semaphore }() // 释放信号量

				// 执行上传
				if err := c.UploadFile(localPath, cosPath); err != nil {
					// 只发送第一个错误
					select {
					case errChan <- fmt.Errorf("上传文件 %s 失败: %v", localPath, err):
					default:
					}
				}
			}(path, cosPath)
		}

		return nil
	})

	if err != nil {
		return fmt.Errorf("遍历目录失败: %v", err)
	}

	// 等待所有上传完成
	wg.Wait()

	// 检查是否有错误
	select {
	case err := <-errChan:
		return err
	default:
		return nil
	}
}
