package upload

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"path"
	"strings"
	"time"
	"unicode"

	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/platform/idgen"
	"hyapp-admin-server/internal/response"

	"github.com/gin-gonic/gin"
)

const (
	uploadSniffBytes = 512
	maxBatchFiles    = 10
)

var (
	imageUploadPolicy = uploadPolicy{
		kind:         "images",
		requireImage: true,
		allowedExtensions: map[string]struct{}{
			".jpg": {}, ".jpeg": {}, ".png": {}, ".webp": {}, ".svga": {}, ".pag": {},
		},
	}
	fileUploadPolicy = uploadPolicy{
		kind: "files",
	}
)

type ObjectUploader interface {
	PutObject(ctx context.Context, key string, reader io.Reader, sizeBytes int64, contentType string) (string, error)
}

type Handler struct {
	audit        shared.OperationLogger
	objectPrefix string
	uploader     ObjectUploader
}

func New(uploader ObjectUploader, objectPrefix string, audit shared.OperationLogger) *Handler {
	objectPrefix = strings.Trim(strings.TrimSpace(objectPrefix), "/")
	if objectPrefix == "" {
		objectPrefix = "admin"
	}
	return &Handler{audit: audit, objectPrefix: objectPrefix, uploader: uploader}
}

func (h *Handler) UploadFile(c *gin.Context) {
	h.uploadObject(c, fileUploadPolicy)
}

func (h *Handler) UploadImage(c *gin.Context) {
	h.uploadObject(c, imageUploadPolicy)
}

func (h *Handler) UploadFilesBatch(c *gin.Context) {
	h.uploadObjects(c, fileUploadPolicy)
}

func (h *Handler) UploadImagesBatch(c *gin.Context) {
	h.uploadObjects(c, imageUploadPolicy)
}

func (h *Handler) uploadObject(c *gin.Context, policy uploadPolicy) {
	if h.uploader == nil {
		response.ServerError(c, "文件上传未配置")
		return
	}
	file, header, ok := parseUploadFile(c)
	if !ok {
		return
	}
	defer file.Close()
	defer func() {
		if c.Request.MultipartForm != nil {
			_ = c.Request.MultipartForm.RemoveAll()
		}
	}()

	if header.Size <= 0 {
		response.BadRequest(c, "文件大小不正确")
		return
	}
	head, contentType, detectedContentType, ok := sniffUploadContent(c, file, header)
	if !ok {
		return
	}
	if policy.requireImage {
		contentType = resolveImageUploadContentType(header.Filename, contentType, detectedContentType)
	}
	if !validateUploadContent(policy, header.Filename, contentType) {
		response.BadRequest(c, "文件类型不支持")
		return
	}

	actor := shared.ActorFromContext(c)
	objectKey := buildUploadObjectKey(h.objectPrefix, policy, actor.UserID, header.Filename, contentType, time.Now())
	objectURL, err := h.uploader.PutObject(c.Request.Context(), objectKey, io.MultiReader(bytes.NewReader(head), file), header.Size, contentType)
	if err != nil {
		response.ServerError(c, "文件上传失败")
		return
	}

	shared.OperationLogWithResourceID(c, h.audit, "upload-file", "cos_objects", objectKey, "success",
		fmt.Sprintf("kind=%s size=%d content_type=%s", policy.kind, header.Size, contentType))
	response.OK(c, uploadResult{
		URL:         objectURL,
		ObjectKey:   objectKey,
		ContentType: contentType,
		SizeBytes:   header.Size,
		Filename:    header.Filename,
	})
}

func (h *Handler) uploadObjects(c *gin.Context, policy uploadPolicy) {
	if h.uploader == nil {
		response.ServerError(c, "文件上传未配置")
		return
	}
	headers, ok := parseUploadFiles(c)
	if !ok {
		return
	}
	defer func() {
		if c.Request.MultipartForm != nil {
			_ = c.Request.MultipartForm.RemoveAll()
		}
	}()

	actor := shared.ActorFromContext(c)
	results := make([]uploadResult, 0, len(headers))
	for _, header := range headers {
		if header.Size <= 0 {
			response.BadRequest(c, "文件大小不正确")
			return
		}
		file, err := header.Open()
		if err != nil {
			response.BadRequest(c, "文件参数不正确")
			return
		}
		head, contentType, detectedContentType, sniffOK := sniffUploadContent(c, file, header)
		if !sniffOK {
			_ = file.Close()
			return
		}
		if policy.requireImage {
			contentType = resolveImageUploadContentType(header.Filename, contentType, detectedContentType)
		}
		if !validateUploadContent(policy, header.Filename, contentType) {
			_ = file.Close()
			response.BadRequest(c, "文件类型不支持")
			return
		}

		objectKey := buildUploadObjectKey(h.objectPrefix, policy, actor.UserID, header.Filename, contentType, time.Now())
		objectURL, err := h.uploader.PutObject(c.Request.Context(), objectKey, io.MultiReader(bytes.NewReader(head), file), header.Size, contentType)
		_ = file.Close()
		if err != nil {
			response.ServerError(c, "文件上传失败")
			return
		}
		results = append(results, uploadResult{
			URL:         objectURL,
			ObjectKey:   objectKey,
			ContentType: contentType,
			SizeBytes:   header.Size,
			Filename:    header.Filename,
		})
	}

	shared.OperationLogWithResourceID(c, h.audit, "batch-upload-file", "cos_objects", "batch", "success",
		fmt.Sprintf("kind=%s count=%d", policy.kind, len(results)))
	response.OK(c, results)
}

func parseUploadFile(c *gin.Context) (multipart.File, *multipart.FileHeader, bool) {
	if err := c.Request.ParseMultipartForm(1 << 20); err != nil {
		response.BadRequest(c, "文件参数不正确")
		return nil, nil, false
	}
	for _, field := range []string{"file", "image", "avatar"} {
		file, header, err := c.Request.FormFile(field)
		if err == nil {
			return file, header, true
		}
	}
	response.BadRequest(c, "文件参数不正确")
	return nil, nil, false
}

func parseUploadFiles(c *gin.Context) ([]*multipart.FileHeader, bool) {
	if err := c.Request.ParseMultipartForm(1 << 20); err != nil {
		response.BadRequest(c, "文件参数不正确")
		return nil, false
	}
	if c.Request.MultipartForm == nil || len(c.Request.MultipartForm.File) == 0 {
		response.BadRequest(c, "文件参数不正确")
		return nil, false
	}

	headers := make([]*multipart.FileHeader, 0, maxBatchFiles)
	for _, field := range []string{"files", "file", "images", "image"} {
		for _, header := range c.Request.MultipartForm.File[field] {
			headers = append(headers, header)
		}
	}
	if len(headers) == 0 {
		response.BadRequest(c, "文件参数不正确")
		return nil, false
	}
	if len(headers) > maxBatchFiles {
		response.BadRequest(c, "一次最多上传 10 个素材")
		return nil, false
	}
	return headers, true
}

func sniffUploadContent(c *gin.Context, file multipart.File, header *multipart.FileHeader) ([]byte, string, string, bool) {
	head := make([]byte, uploadSniffBytes)
	n, err := io.ReadFull(file, head)
	if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
		response.BadRequest(c, "文件参数不正确")
		return nil, "", "", false
	}
	head = head[:n]

	detectedContentType := http.DetectContentType(head)
	contentType := strings.TrimSpace(header.Header.Get("Content-Type"))
	if contentType == "" || contentType == "application/octet-stream" {
		contentType = detectedContentType
	}
	return head, contentType, detectedContentType, true
}

func validateUploadContent(policy uploadPolicy, filename string, contentType string) bool {
	contentType = strings.ToLower(strings.TrimSpace(contentType))
	if policy.requireImage && !isImageContentType(contentType) {
		return false
	}
	if len(policy.allowedExtensions) == 0 {
		return true
	}
	extension := normalizedUploadExtension(filename, contentType)
	_, ok := policy.allowedExtensions[extension]
	return ok
}

func resolveImageUploadContentType(filename string, contentType string, detectedContentType string) string {
	extension := cleanUploadExtension(path.Ext(filename))
	switch extension {
	case ".svga":
		return "application/x-svga"
	case ".pag":
		return "application/vnd.tencent.pag"
	}
	contentType = strings.ToLower(strings.TrimSpace(contentType))
	if contentType == "application/x-svga" || contentType == "application/vnd.tencent.pag" {
		return contentType
	}
	if isRasterImageContentType(detectedContentType) {
		return strings.ToLower(strings.TrimSpace(detectedContentType))
	}
	return strings.ToLower(strings.TrimSpace(detectedContentType))
}

func isImageContentType(contentType string) bool {
	if isRasterImageContentType(contentType) {
		return true
	}
	switch strings.ToLower(strings.TrimSpace(contentType)) {
	case "application/x-svga", "application/vnd.tencent.pag":
		return true
	default:
		return false
	}
}

func isRasterImageContentType(contentType string) bool {
	switch strings.ToLower(strings.TrimSpace(contentType)) {
	case "image/jpeg", "image/png", "image/webp":
		return true
	default:
		return false
	}
}

func buildUploadObjectKey(objectPrefix string, policy uploadPolicy, actorID uint, filename string, contentType string, now time.Time) string {
	date := now.UTC().Format("20060102")
	extension := normalizedUploadExtension(filename, contentType)
	if extension == "" && policy.requireImage {
		extension = ".jpg"
	}

	idPrefix := strings.TrimSuffix(policy.kind, "s")
	return fmt.Sprintf("%s/%s/%d/%s/%s%s", objectPrefix, policy.kind, actorID, date, idgen.New(idPrefix), extension)
}

func normalizedUploadExtension(filename string, contentType string) string {
	if extension := cleanUploadExtension(path.Ext(filename)); extension != "" {
		return extension
	}

	switch strings.ToLower(strings.TrimSpace(contentType)) {
	case "image/jpeg":
		return ".jpg"
	case "image/png":
		return ".png"
	case "image/webp":
		return ".webp"
	case "application/x-svga":
		return ".svga"
	case "application/vnd.tencent.pag":
		return ".pag"
	case "application/pdf":
		return ".pdf"
	default:
		return ""
	}
}

func cleanUploadExtension(extension string) string {
	extension = strings.ToLower(strings.TrimSpace(extension))
	if len(extension) < 2 || len(extension) > 10 || extension[0] != '.' {
		return ""
	}
	for _, char := range extension[1:] {
		if !unicode.IsDigit(char) && (char < 'a' || char > 'z') {
			return ""
		}
	}
	return extension
}
