package qiniu

import (
	"bytes"
	"context"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"time"

	"u-desk/internal/oss"
)

// UploadResult 七牛云上传结果
type qiniuUploadResult struct {
	Key    string `json:"key"`
	Hash   string `json:"hash"`
	Size   int64  `json:"fsize"`
	Bucket string `json:"bucket"`
}

// UploadWithUploader 使用表单上传文件
func (c *Client) UploadWithUploader(ctx context.Context, key string, reader io.Reader, options *oss.UploadOptions) (*oss.UploadResult, error) {
	// 生成上传 token
	token := c.generateUploadToken(key)

	// 创建 multipart form
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	// 添加字段
	_ = writer.WriteField("token", token)
	_ = writer.WriteField("key", key)

	// 添加文件
	part, err := writer.CreateFormFile("file", key)
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to create form file", err)
	}

	// 读取数据并写入
	// 为了获取文件大小，先读取到内存
	data, err := io.ReadAll(reader)
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to read file data", err)
	}

	if _, err := part.Write(data); err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to write file data", err)
	}

	// 设置 Content-Type
	if options != nil && options.ContentType != "" {
		_ = writer.WriteField("mimeType", options.ContentType)
	}

	if err := writer.Close(); err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to close multipart writer", err)
	}

	// 上传 URL - 根据配置或区域选择
	uploadURL := c.getUploadDomain()

	// 创建请求
	req, err := http.NewRequestWithContext(ctx, "POST", uploadURL, body)
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to create request", err)
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())

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

	// 读取响应
	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to read response", err)
	}

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

	// 解析结果
	var result qiniuUploadResult
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to parse response", err)
	}

	return &oss.UploadResult{
		Key:  result.Key,
		ETag: result.Hash,
		Size: result.Size,
	}, nil
}

// UploadClient 专用的上传客户端
type UploadClient struct {
	config *Config
	client *http.Client
}

// NewUploadClient 创建上传客户端
func NewUploadClient(config *Config) *UploadClient {
	return &UploadClient{
		config: config,
		client: &http.Client{
			Timeout: 5 * time.Minute,
		},
	}
}

// Upload 上传文件
func (uc *UploadClient) Upload(ctx context.Context, key string, reader io.Reader) (*oss.UploadResult, error) {
	token := uc.generateUploadToken()

	// 创建 multipart form
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	_ = writer.WriteField("token", token)
	_ = writer.WriteField("key", key)

	part, err := writer.CreateFormFile("file", key)
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to create form file", err)
	}

	data, err := io.ReadAll(reader)
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to read file data", err)
	}

	if _, err := part.Write(data); err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to write file data", err)
	}

	if err := writer.Close(); err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to close multipart writer", err)
	}

	// 上传 URL - 根据配置或区域选择
	scheme := "https://"
	if !uc.config.UseHTTPS {
		scheme = "http://"
	}

	var uploadURL string
	if uc.config.UploadDomain != "" {
		uploadURL = scheme + uc.config.UploadDomain
	} else {
		// 根据区域选择
		switch uc.config.Region {
		case "z0":
			uploadURL = scheme + "up-z0.qiniup.com"
		case "z1":
			uploadURL = scheme + "up-z1.qiniup.com"
		case "z2":
			uploadURL = scheme + "up-z2.qiniup.com"
		case "na0":
			uploadURL = scheme + "up-na0.qiniup.com"
		case "as0":
			uploadURL = scheme + "up-as0.qiniup.com"
		default:
			uploadURL = scheme + "up-z0.qiniup.com"
		}
	}

	req, err := http.NewRequestWithContext(ctx, "POST", uploadURL, body)
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to create request", err)
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())

	resp, err := uc.client.Do(req)
	if err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to upload file", err)
	}
	defer resp.Body.Close()

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

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

	var result qiniuUploadResult
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, oss.NewError("UPLOAD_ERROR", "failed to parse response", err)
	}

	return &oss.UploadResult{
		Key:  result.Key,
		ETag: result.Hash,
		Size: result.Size,
	}, nil
}

// generateUploadToken 生成上传 token
func (uc *UploadClient) generateUploadToken() string {
	// 1. 创建 putPolicy
	putPolicy := fmt.Sprintf(`{"scope":"%s","deadline":%d}`,
		uc.config.Bucket, time.Now().Add(1*time.Hour).Unix())

	// 2. 对 putPolicy 进行 base64 URL 编码
	encodedPutPolicy := base64.URLEncoding.EncodeToString([]byte(putPolicy))

	// 3. 对 encodedPutPolicy 进行 HMAC-SHA1 签名
	h := uc.hmacSHA1([]byte(encodedPutPolicy))
	encodedSign := base64.URLEncoding.EncodeToString(h)

	// 4. 组合 token
	return uc.config.AccessKey + ":" + encodedSign + ":" + encodedPutPolicy
}

// hmacSHA1 HMAC-SHA1 签名
func (uc *UploadClient) hmacSHA1(data []byte) []byte {
	h := hmac.New(sha1.New, []byte(uc.config.SecretKey))
	h.Write(data)
	return h.Sum(nil)
}
