package files

import (
	"errors"
	"log"
	"net/http"
	"path/filepath"
	"time"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
	"senlinai-agent/backend/internal/httpx"
	"senlinai-agent/backend/internal/logic/auth"
	"senlinai-agent/backend/internal/logic/projects"
	"senlinai-agent/backend/internal/models"
)

// SourceDTO 是文件资料写接口的稳定响应，仅返回 opaque storage key，不暴露物理目录布局。
type SourceDTO struct {
	ID         string    `json:"id"`
	ProjectID  string    `json:"projectId"`
	Kind       string    `json:"kind"`
	Title      string    `json:"title"`
	StorageKey string    `json:"storageKey"`
	CreatedAt  time.Time `json:"createdAt"`
	UpdatedAt  time.Time `json:"updatedAt"`
}

// Handler 负责 multipart 边界，文件名清理和本地路径构造始终委托给 Service。
type Handler struct {
	service        *Service
	maxUploadBytes int64
}

// DefaultMaxUploadBytes 是未显式配置时的请求体上限，覆盖完整 multipart 内容。
const DefaultMaxUploadBytes int64 = 32 << 20

// NewHandler 创建文件资料 HTTP registrar。
func NewHandler(service *Service, limits ...int64) *Handler {
	limit := DefaultMaxUploadBytes
	if len(limits) > 0 && limits[0] > 0 {
		limit = limits[0]
	}
	return &Handler{service: service, maxUploadBytes: limit}
}

// Register 将文件资料上传接口注册到上层提供的 /api/v1 路由组。
func (h *Handler) Register(router gin.IRouter) {
	router.POST("/projects/:id/sources", h.upload)
}

func (h *Handler) upload(c *gin.Context) {
	userID, ok := auth.CurrentUserID(c)
	if !ok {
		httpx.Error(c, http.StatusUnauthorized, "unauthorized", "未登录或登录已失效")
		return
	}
	projectIdentity, ok := httpx.IdentityParam(c, "id")
	if !ok {
		return
	}
	// 所有权必须在写磁盘前校验，防止越权请求留下孤立文件。
	project, err := projects.FindOwnedProject(userID, projectIdentity)
	if err != nil {
		writeSourceError(c, err)
		return
	}
	// MaxBytesReader 必须在任何 multipart 解析前安装，限制字段、边界和文件内容的总请求量。
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, h.maxUploadBytes)
	fileHeader, err := c.FormFile("file")
	if err != nil {
		var maxBytesError *http.MaxBytesError
		if errors.As(err, &maxBytesError) {
			httpx.Error(c, http.StatusRequestEntityTooLarge, "payload_too_large", "上传内容超过大小限制")
			return
		}
		httpx.Error(c, http.StatusBadRequest, "invalid_request", "请选择要上传的文件")
		return
	}
	file, err := fileHeader.Open()
	if err != nil {
		httpx.Error(c, http.StatusBadRequest, "invalid_request", "文件读取失败")
		return
	}
	defer file.Close()

	// 先由文件服务保存内容，再用其返回的相对路径创建资料记录；handler 不拼接任何本地路径。
	stored, err := h.service.Save(project.Identity, fileHeader.Filename, file)
	if err != nil {
		httpx.Error(c, http.StatusInternalServerError, "internal_error", "文件保存失败")
		return
	}
	source, err := h.service.CreateSource(userID, project, c.PostForm("title"), stored)
	if err != nil {
		if cleanupErr := h.service.Remove(stored); cleanupErr != nil {
			// 清理错误仅写服务端日志，响应继续使用稳定中文错误，不暴露 storage root。
			log.Printf("source file cleanup failed: %v", cleanupErr)
		}
		writeSourceError(c, err)
		return
	}
	c.JSON(http.StatusCreated, sourceDTO(*source))
}

func sourceDTO(source models.SenlinAgentSource) SourceDTO {
	return SourceDTO{
		ID: source.Identity, ProjectID: source.ProjectIdentity, Kind: source.Kind,
		Title: source.Title, StorageKey: filepath.Base(filepath.FromSlash(source.FilePath)),
		CreatedAt: source.CreatedAt.UTC(), UpdatedAt: source.UpdatedAt.UTC(),
	}
}

func writeSourceError(c *gin.Context, err error) {
	switch {
	case errors.Is(err, gorm.ErrRecordNotFound):
		httpx.Error(c, http.StatusNotFound, "not_found", "项目不存在")
	case errors.Is(err, ErrSourceTitleRequired), errors.Is(err, ErrSourcePathRequired):
		httpx.Error(c, http.StatusBadRequest, "invalid_request", "资料参数无效")
	default:
		httpx.Error(c, http.StatusInternalServerError, "internal_error", "资料创建失败")
	}
}
