package aliyun

import (
	"context"
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"u-desk/internal/oss"
)

// Config 阿里云 OSS 配置
type Config struct {
	AccessKeyID     string // 访问密钥 ID
	AccessKeySecret string // 访问密钥 Secret
	Bucket          string // 存储空间名称
	Region          string // 区域，如 oss-cn-hangzhou
	Endpoint        string // 自定义 Endpoint（可选）
	UseHTTPS        bool   // 是否使用 HTTPS
}

// Client 阿里云 OSS 客户端
type Client struct {
	config     *Config
	httpClient *http.Client
}

// NewClient 创建阿里云 OSS 客户端
func NewClient(config *Config) (*Client, error) {
	if config == nil {
		return nil, oss.NewError("INVALID_CONFIG", "config cannot be nil", nil)
	}
	if config.AccessKeyID == "" || config.AccessKeySecret == "" {
		return nil, oss.NewError("INVALID_CONFIG", "access key id and secret are required", nil)
	}
	if config.Bucket == "" {
		return nil, oss.NewError("INVALID_CONFIG", "bucket name is required", nil)
	}

	// 设置默认区域
	if config.Region == "" {
		config.Region = "oss-cn-hangzhou" // 默认华东1（杭州）
	}

	// 构建 Endpoint
	if config.Endpoint == "" {
		config.Endpoint = config.Region + ".aliyuncs.com"
	}

	return &Client{
		config:     config,
		httpClient: &http.Client{Timeout: 30 * time.Second},
	}, nil
}

// generateSignature 生成阿里云 OSS 签名
// 参考: https://help.aliyun.com/zh/oss/developer-reference/signature-detail
func (c *Client) generateSignature(method, path, contentType, date string, contentMD5 string) string {
	// 构建待签名字符串
	// StringToSign = HTTP-Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" + CanonicalizedResource
	stringToSign := method + "\n"
	stringToSign += contentMD5 + "\n"
	stringToSign += contentType + "\n"
	stringToSign += date + "\n"
	stringToSign += path

	// 使用 HMAC-SHA1 签名
	h := hmac.New(sha1.New, []byte(c.config.AccessKeySecret))
	h.Write([]byte(stringToSign))
	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))

	return signature
}

// generateSignatureWithHeaders 生成包含自定义头的签名
// 用于需要包含 x-oss-* 头的请求（如 CopyObject）
func (c *Client) generateSignatureWithHeaders(method, path, contentType, date string, headers map[string]string) string {
	// 构建待签名字符串
	stringToSign := method + "\n"
	stringToSign += "\n" // Content-MD5 (空)
	stringToSign += contentType + "\n"
	stringToSign += date + "\n"

	// 添加 CanonicalizedOSSHeaders (以 x-oss- 开头的头)
	ossHeaders := c.canonicalizeOSSHeaders(headers)
	stringToSign += ossHeaders

	// 添加 CanonicalizedResource
	stringToSign += path

	// 使用 HMAC-SHA1 签名
	h := hmac.New(sha1.New, []byte(c.config.AccessKeySecret))
	h.Write([]byte(stringToSign))
	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))

	return signature
}

// canonicalizeOSSHeaders 规范化 OSS 自定义头
// 将所有以 x-oss- 开头的头按字典序排序，并转换为小写
func (c *Client) canonicalizeOSSHeaders(headers map[string]string) string {
	if len(headers) == 0 {
		return ""
	}

	// 提取以 x-oss- 开头的头
	var ossHeaders []string
	for k, v := range headers {
		if strings.HasPrefix(strings.ToLower(k), "x-oss-") {
			// 转换为小写并添加到列表
			lowerKey := strings.ToLower(k)
			ossHeaders = append(ossHeaders, lowerKey+":"+v)
		}
	}

	// 按字典序排序
	// 这里简单处理，实际应该用排序算法
	for i := 0; i < len(ossHeaders); i++ {
		for j := i + 1; j < len(ossHeaders); j++ {
			if ossHeaders[i] > ossHeaders[j] {
				ossHeaders[i], ossHeaders[j] = ossHeaders[j], ossHeaders[i]
			}
		}
	}

	// 拼接结果
	if len(ossHeaders) == 0 {
		return ""
	}

	result := ""
	for _, h := range ossHeaders {
		result += h + "\n"
	}

	return result
}

// Upload 上传文件
func (c *Client) Upload(ctx context.Context, key string, reader io.Reader, options *oss.UploadOptions) (*oss.UploadResult, error) {
	// 读取数据
	data, err := io.ReadAll(reader)
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to read data", err)
	}

	// 计算 Content-MD5
	hash := md5.Sum(data)
	contentMD5 := base64.StdEncoding.EncodeToString(hash[:])

	// 设置 Content-Type
	contentType := "application/octet-stream"
	if options != nil && options.ContentType != "" {
		contentType = options.ContentType
	}

	// 构建请求
	date := time.Now().UTC().Format(http.TimeFormat)
	path := "/" + c.config.Bucket + "/" + key
	signature := c.generateSignature("PUT", path, contentType, date, contentMD5)

	scheme := "https://"
	if !c.config.UseHTTPS {
		scheme = "http://"
	}
	url := scheme + c.config.Bucket + "." + c.config.Endpoint + "/" + key

	req, err := http.NewRequestWithContext(ctx, "PUT", url, strings.NewReader(string(data)))
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to create request", err)
	}

	req.Header.Set("Date", date)
	req.Header.Set("Authorization", "OSS "+c.config.AccessKeyID+":"+signature)
	req.Header.Set("Content-Type", contentType)
	req.Header.Set("Content-MD5", contentMD5)

	// 发送请求
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to upload file", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		body, _ := io.ReadAll(resp.Body)
		return nil, oss.NewError("UPLOAD_ERROR",
			fmt.Sprintf("upload failed with status %d: %s", resp.StatusCode, string(body)), nil)
	}

	// 获取 ETag
	etag := resp.Header.Get("ETag")
	// 去掉 ETag 的引号
	etag = strings.Trim(etag, "\"")

	return &oss.UploadResult{
		Key:  key,
		ETag: etag,
		Size: int64(len(data)),
	}, nil
}

// Download 下载文件
func (c *Client) Download(ctx context.Context, key string, writer io.Writer) error {
	date := time.Now().UTC().Format(http.TimeFormat)
	path := "/" + c.config.Bucket + "/" + key
	signature := c.generateSignature("GET", path, "", date, "")

	scheme := "https://"
	if !c.config.UseHTTPS {
		scheme = "http://"
	}
	url := scheme + c.config.Bucket + "." + c.config.Endpoint + "/" + key

	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return oss.NewError("DOWNLOAD_ERROR", "failed to create request", err)
	}

	req.Header.Set("Date", date)
	req.Header.Set("Authorization", "OSS "+c.config.AccessKeyID+":"+signature)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return oss.NewError("DOWNLOAD_ERROR", "failed to download file", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return oss.NewError("DOWNLOAD_ERROR", fmt.Sprintf("download failed with status %d", resp.StatusCode), nil)
	}

	_, err = io.Copy(writer, resp.Body)
	return err
}

// Delete 删除文件
func (c *Client) Delete(ctx context.Context, key string) error {
	date := time.Now().UTC().Format(http.TimeFormat)
	path := "/" + c.config.Bucket + "/" + key
	signature := c.generateSignature("DELETE", path, "", date, "")

	scheme := "https://"
	if !c.config.UseHTTPS {
		scheme = "http://"
	}
	url := scheme + c.config.Bucket + "." + c.config.Endpoint + "/" + key

	req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil)
	if err != nil {
		return oss.NewError("DELETE_ERROR", "failed to create request", err)
	}

	req.Header.Set("Date", date)
	req.Header.Set("Authorization", "OSS "+c.config.AccessKeyID+":"+signature)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return oss.NewError("DELETE_ERROR", "failed to delete file", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 204 {
		body, _ := io.ReadAll(resp.Body)
		return oss.NewError("DELETE_ERROR",
			fmt.Sprintf("delete failed with status %d: %s", resp.StatusCode, string(body)), nil)
	}

	return nil
}

// GetFileInfo 获取文件信息
func (c *Client) GetFileInfo(ctx context.Context, key string) (*oss.FileInfo, error) {
	date := time.Now().UTC().Format(http.TimeFormat)
	path := "/" + c.config.Bucket + "/" + key
	signature := c.generateSignature("HEAD", path, "", date, "")

	scheme := "https://"
	if !c.config.UseHTTPS {
		scheme = "http://"
	}
	url := scheme + c.config.Bucket + "." + c.config.Endpoint + "/" + key

	req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
	if err != nil {
		return nil, oss.NewError("STAT_ERROR", "failed to create request", err)
	}

	req.Header.Set("Date", date)
	req.Header.Set("Authorization", "OSS "+c.config.AccessKeyID+":"+signature)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, oss.NewError("STAT_ERROR", "failed to get file info", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == 404 {
		return nil, oss.ErrFileNotFound
	}

	if resp.StatusCode != 200 {
		return nil, oss.NewError("STAT_ERROR", fmt.Sprintf("stat failed with status %d", resp.StatusCode), nil)
	}

	// 解析响应头
	size := resp.ContentLength
	etag := resp.Header.Get("ETag")
	contentType := resp.Header.Get("Content-Type")
	_ = resp.Header.Get("Last-Modified") // 预留，可能需要解析时间

	return &oss.FileInfo{
		Key:         key,
		Size:        size,
		ETag:        strings.Trim(etag, "\""),
		ContentType: contentType,
	}, nil
}

// ListFiles 列举文件
func (c *Client) ListFiles(ctx context.Context, options *oss.ListOptions) (*oss.ListResult, error) {
	if options == nil {
		options = &oss.ListOptions{}
	}

	if options.MaxKeys == 0 {
		options.MaxKeys = 100
	}

	date := time.Now().UTC().Format(http.TimeFormat)

	scheme := "https://"
	if !c.config.UseHTTPS {
		scheme = "http://"
	}

	// 构建查询参数（URL 编码）
	query := url.Values{}
	query.Set("max-keys", fmt.Sprintf("%d", options.MaxKeys))
	if options.Prefix != "" {
		query.Set("prefix", options.Prefix)
	}
	if options.Marker != "" {
		query.Set("marker", options.Marker)
	}
	if options.Delimiter != "" {
		query.Set("delimiter", options.Delimiter)
	}

	// CanonicalizedResource: list 参数(prefix/delimiter/marker/max-keys)不是子资源，不参与签名
	signPath := "/" + c.config.Bucket + "/"
	signature := c.generateSignature("GET", signPath, "", date, "")

	requestURL := scheme + c.config.Bucket + "." + c.config.Endpoint + "/?" + query.Encode()

	req, err := http.NewRequestWithContext(ctx, "GET", requestURL, nil)
	if err != nil {
		return nil, oss.NewError("LIST_ERROR", "failed to create request", err)
	}

	req.Header.Set("Date", date)
	req.Header.Set("Authorization", "OSS "+c.config.AccessKeyID+":"+signature)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, oss.NewError("LIST_ERROR", "failed to list files", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, oss.NewError("LIST_ERROR", "failed to read response", err)
	}

	if resp.StatusCode != 200 {
		return nil, oss.NewError("LIST_ERROR",
			fmt.Sprintf("list failed with status %d: %s", resp.StatusCode, string(body)), nil)
	}

	// 解析 XML 响应
	var result ListBucketResult
	if err := xml.Unmarshal(body, &result); err != nil {
		return nil, oss.NewError("LIST_ERROR", "failed to parse response", err)
	}

	// 转换为统一格式
	files := make([]oss.FileInfo, 0, len(result.Contents))
	for _, obj := range result.Contents {
		lastMod := parseAliyunTime(obj.LastModified)
		files = append(files, oss.FileInfo{
			Key:          obj.Key,
			Size:         obj.Size,
			ETag:         strings.Trim(obj.ETag, "\""),
			LastModified: lastMod,
		})
	}

	prefixes := make([]string, 0)
	for _, p := range result.CommonPrefixes.Prefix {
		prefixes = append(prefixes, p)
	}

	return &oss.ListResult{
		Files:       files,
		IsTruncated: result.IsTruncated,
		NextMarker:  result.NextMarker,
		Prefixes:    prefixes,
	}, nil
}

// Copy 复制文件
func (c *Client) Copy(ctx context.Context, sourceKey, targetKey string) error {
	date := time.Now().UTC().Format(http.TimeFormat)
	path := "/" + c.config.Bucket + "/" + targetKey

	// 设置自定义头
	headers := map[string]string{
		"x-oss-copy-source": "/" + c.config.Bucket + "/" + sourceKey,
	}
	signature := c.generateSignatureWithHeaders("PUT", path, "", date, headers)

	scheme := "https://"
	if !c.config.UseHTTPS {
		scheme = "http://"
	}
	url := scheme + c.config.Bucket + "." + c.config.Endpoint + "/" + targetKey

	req, err := http.NewRequestWithContext(ctx, "PUT", url, nil)
	if err != nil {
		return oss.NewError("COPY_ERROR", "failed to create request", err)
	}

	// 设置头
	req.Header.Set("Date", date)
	req.Header.Set("Authorization", "OSS "+c.config.AccessKeyID+":"+signature)
	req.Header.Set("x-oss-copy-source", "/"+c.config.Bucket+"/"+sourceKey)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return oss.NewError("COPY_ERROR", "failed to copy file", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		body, _ := io.ReadAll(resp.Body)
		return oss.NewError("COPY_ERROR",
			fmt.Sprintf("copy failed with status %d: %s", resp.StatusCode, string(body)), nil)
	}

	return nil
}

// Move 移动/重命名文件
func (c *Client) Move(ctx context.Context, sourceKey, targetKey string) error {
	// 阿里云 OSS 通过复制 + 删除实现移动
	if err := c.Copy(ctx, sourceKey, targetKey); err != nil {
		return err
	}
	return c.Delete(ctx, sourceKey)
}

// DeleteMultiple 批量删除文件
func (c *Client) DeleteMultiple(ctx context.Context, keys []string) (*oss.DeleteResult, error) {
	result := &oss.DeleteResult{
		Deleted: make([]string, 0),
		Errors:  make([]string, 0),
	}

	for _, key := range keys {
		if err := c.Delete(ctx, key); err != nil {
			result.Errors = append(result.Errors, key)
		} else {
			result.Deleted = append(result.Deleted, key)
		}
	}

	return result, nil
}

// GetSignedURL 获取预签名URL
func (c *Client) GetSignedURL(ctx context.Context, key string, expiresIn time.Duration) (string, error) {
	// 阿里云 OSS 使用签名 URL
	// 格式: ?OSSAccessKeyId=xxx&Expires=xxx&Signature=xxx
	expiration := time.Now().Add(expiresIn).Unix()

	// 构建签名
	path := "/" + c.config.Bucket + "/" + key
	stringToSign := "GET\n\n\n" + fmt.Sprintf("%d", expiration) + "\n" + path
	h := hmac.New(sha1.New, []byte(c.config.AccessKeySecret))
	h.Write([]byte(stringToSign))
	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
	signature = strings.TrimRight(signature, "=") // URL Safe

	scheme := "https://"
	if !c.config.UseHTTPS {
		scheme = "http://"
	}
	baseURL := scheme + c.config.Bucket + "." + c.config.Endpoint + "/" + key

	signedURL := fmt.Sprintf("%s?OSSAccessKeyId=%s&Expires=%d&Signature=%s",
		baseURL, c.config.AccessKeyID, expiration, signature)

	return signedURL, nil
}

// Exists 检查文件是否存在
func (c *Client) Exists(ctx context.Context, key string) (bool, error) {
	_, err := c.GetFileInfo(ctx, key)
	if err == oss.ErrFileNotFound {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return true, nil
}

// Close 关闭连接
func (c *Client) Close() error {
	c.httpClient.CloseIdleConnections()
	return nil
}

// parseAliyunTime 宽容解析阿里云时间格式
func parseAliyunTime(s string) time.Time {
	for _, layout := range []string{
		"2006-01-02T15:04:05.000Z",
		"2006-01-02T15:04:05Z",
		"2006-01-02T15:04:05.000000Z",
		time.RFC3339,
	} {
		if t, err := time.Parse(layout, s); err == nil {
			return t
		}
	}
	return time.Time{}
}

// ============ XML 数据结构 ============

// ListBucketResult 列举 Bucket 响应
// 阿里云 XML: 每个 <Contents> 直接包含 Key/Size 等字段，无 <Object> 包裹
type ListBucketResult struct {
	XMLName     xml.Name `xml:"ListBucketResult"`
	Name        string   `xml:"Name"`
	Prefix      string   `xml:"Prefix"`
	Marker      string   `xml:"Marker"`
	MaxKeys     int      `xml:"MaxKeys"`
	IsTruncated bool     `xml:"IsTruncated"`
	NextMarker  string   `xml:"NextMarker"`
	Delimiter   string   `xml:"Delimiter"`
	Contents    []Object `xml:"Contents"`
	CommonPrefixes struct {
		Prefix []string `xml:"Prefix"`
	} `xml:"CommonPrefixes"`
}

type Object struct {
	Key          string `xml:"Key"`
	LastModified string `xml:"LastModified"`
	ETag         string `xml:"ETag"`
	Size         int64  `xml:"Size"`
	StorageClass string `xml:"StorageClass"`
}
