package upload

import (
	"context"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"tyc-server/app/main/api/internal/svc"
	"tyc-server/app/main/api/internal/types"
	"tyc-server/common/xerr"

	"github.com/pkg/errors"
	"github.com/zeromicro/go-zero/core/logx"
)

const maxImageSize = 3 * 1024 * 1024 // 3MB
const defaultTempFileMaxAgeH = 24

type UploadImageLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewUploadImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadImageLogic {
	return &UploadImageLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *UploadImageLogic) UploadImage(req *types.UploadImageReq) (resp *types.UploadImageResp, err error) {
	decoded, decErr := base64.StdEncoding.DecodeString(req.ImageBase64)
	if decErr != nil {
		return nil, errors.Wrapf(xerr.NewErrMsg("图片 base64 格式错误"), "%v", decErr)
	}
	if len(decoded) > maxImageSize {
		return nil, errors.Wrapf(xerr.NewErrMsg("图片不能超过 3M"), "size=%d", len(decoded))
	}

	// 按文件内容 hash 命名，相同文件复用同一 URL，避免重复传输与刷流量
	hashSum := sha256.Sum256(decoded)
	hashHex := hex.EncodeToString(hashSum[:])
	fileName := hashHex + ".jpg"

	dir := l.uploadStoragePath()
	if err := os.MkdirAll(dir, 0755); err != nil {
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "创建上传目录失败: %v", err)
	}

	filePath := filepath.Join(dir, fileName)
	// 若已存在同 hash 文件，直接返回 URL，不重复写入
	if _, statErr := os.Stat(filePath); statErr == nil {
		url := l.buildURL(fileName)
		logx.Infof("upload image dedup by hash, file=%s", fileName)
		return &types.UploadImageResp{Url: url}, nil
	}

	if err := os.WriteFile(filePath, decoded, 0644); err != nil {
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "保存图片失败: %v", err)
	}

	// 异步清理过期临时文件，不阻塞响应
	go l.deleteOldUploads(dir)

	url := l.buildURL(fileName)
	logx.Infof("upload image ok, file=%s", fileName)
	return &types.UploadImageResp{Url: url}, nil
}

func (l *UploadImageLogic) buildURL(fileName string) string {
	baseURL := l.svcCtx.Config.Upload.FileBaseURL
	if baseURL == "" && l.svcCtx.Config.PublicBaseURL != "" {
		// 兜底：如果未单独配置 Upload.FileBaseURL，则使用公共域名拼接默认上传路径
		baseURL = strings.TrimRight(l.svcCtx.Config.PublicBaseURL, "/") + "/api/v1/upload/file"
	}
	if baseURL == "" {
		return ""
	}
	return fmt.Sprintf("%s/%s", baseURL, fileName)
}

func (l *UploadImageLogic) uploadStoragePath() string {
	candidates := []string{
		"data/uploads",
		"../data/uploads",
		"../../data/uploads",
		"../../../data/uploads",
	}
	for _, c := range candidates {
		abs, _ := filepath.Abs(c)
		if err := os.MkdirAll(abs, 0755); err == nil {
			return abs
		}
	}
	abs, _ := filepath.Abs(candidates[0])
	return abs
}

// deleteOldUploads 删除目录下超过保留时长的临时文件
func (l *UploadImageLogic) deleteOldUploads(dir string) {
	maxAgeH := l.svcCtx.Config.Upload.TempFileMaxAgeH
	if maxAgeH <= 0 {
		maxAgeH = defaultTempFileMaxAgeH
	}
	cutoff := time.Now().Add(-time.Duration(maxAgeH) * time.Hour)

	entries, err := os.ReadDir(dir)
	if err != nil {
		l.Errorf("deleteOldUploads ReadDir: %v", err)
		return
	}
	for _, e := range entries {
		if e.IsDir() {
			continue
		}
		path := filepath.Join(dir, e.Name())
		info, err := os.Stat(path)
		if err != nil {
			continue
		}
		if info.ModTime().Before(cutoff) {
			if err := os.Remove(path); err != nil {
				l.Errorf("deleteOldUploads Remove %s: %v", path, err)
			} else {
				l.Infof("deleteOldUploads removed %s", path)
			}
		}
	}
}
