package agent

import (
	"errors"
	"fmt"
	"io"
	"mime"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/store/attachment"
)

const (
	maxAgentUploadSizeB     = 10 * 1024 * 1024
	maxAgentUploadSizeLabel = "10 MiB"
)

// FileHandler owns agent attachment and avatar upload endpoints.
type FileHandler struct {
	auth   AgentAuthenticator
	files  FileStore
	agents AgentAvatarStore
}

// The embedded camelCase DTO is authoritative. This alias only preserves
// compatibility with released daemons and is derived from the same value.
type agentAvatarUploadCompatibilityResponse struct {
	dto.AgentAvatarUploadResponse
	LegacyURL string `json:"url"`
}

func avatarUploadCompatibilityResponse(response dto.AgentAvatarUploadResponse) agentAvatarUploadCompatibilityResponse {
	return agentAvatarUploadCompatibilityResponse{
		AgentAvatarUploadResponse: response,
		LegacyURL:                 response.AvatarURL,
	}
}

// Upload handles the canonical Agent Runtime upload route.
// It stores one file attachment for a scope visible to the authenticated agent.
func (h *FileHandler) Upload(c *gin.Context) {
	agent, ok := authenticate(c, h.auth)
	if !ok {
		return
	}

	const (
		maxUploadMemForm = 8 * 1024 * 1024
		maxUploadBodyB   = maxUploadMemForm + maxAgentUploadSizeB
	)

	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxUploadBodyB)
	if err := c.Request.ParseMultipartForm(maxUploadMemForm); err != nil {
		status := http.StatusBadRequest
		var maxBytesError *http.MaxBytesError
		if errors.As(err, &maxBytesError) {
			status = http.StatusRequestEntityTooLarge
		}
		c.JSON(status, agentError("invalid multipart form"))
		return
	}
	if c.Request.MultipartForm != nil {
		defer func() {
			_ = c.Request.MultipartForm.RemoveAll()
		}()
	}

	scopeID := agentUploadScopeID(c)
	if scopeID == "" {
		c.JSON(http.StatusBadRequest, agentError("scopeId is required"))
		return
	}

	files := c.Request.MultipartForm.File["file"]
	if len(files) != 1 {
		c.JSON(http.StatusBadRequest, agentError("file is required"))
		return
	}

	header := files[0]
	opened, err := header.Open()
	if err != nil {
		c.JSON(http.StatusBadRequest, agentError("invalid file upload"))
		return
	}
	defer func() { _ = opened.Close() }()

	limited := io.LimitReader(opened, maxAgentUploadSizeB+1)
	blob, err := io.ReadAll(limited)
	if err != nil {
		c.JSON(http.StatusBadRequest, agentError("failed to read uploaded file"))
		return
	}
	if len(blob) > maxAgentUploadSizeB {
		c.JSON(http.StatusBadRequest, agentError(fmt.Sprintf("file too large (max %s)", maxAgentUploadSizeLabel)))
		return
	}

	contentType := attachmentUploadContentType(header.Filename, header.Header.Get("Content-Type"), blob)

	created, err := h.files.UploadAttachmentsForMember(c.Request.Context(), agent.MemberID, agent.WorkspaceID, scopeID, createAttachmentParamsList(header.Filename, contentType, blob))
	if err != nil {
		c.JSON(HTTPStatus(err), agentError(err.Error()))
		return
	}
	if len(created) != 1 {
		c.JSON(http.StatusInternalServerError, agentError("invalid upload response"))
		return
	}

	c.JSON(http.StatusOK, dto.AgentFileUploadResponse{
		ID:          created[0].ID,
		Filename:    created[0].Filename,
		ContentType: contentType,
		SizeBytes:   int64(len(blob)),
	})
}

func agentUploadScopeID(c *gin.Context) string {
	// scopeId is the only accepted multipart target field for agent uploads.
	return strings.TrimSpace(c.PostForm("scopeId"))
}

func attachmentUploadContentType(filename string, contentType string, blob []byte) string {
	contentType = strings.TrimSpace(contentType)
	if contentType != "" && contentType != "application/octet-stream" {
		return contentType
	}
	if byExt := mime.TypeByExtension(strings.ToLower(filepath.Ext(filename))); strings.TrimSpace(byExt) != "" {
		return byExt
	}
	if len(blob) > 0 {
		return http.DetectContentType(blob)
	}
	return "application/octet-stream"
}

// Avatar handles the canonical Agent Runtime avatar route.
// It stores one image and updates the authenticated agent's avatar URL.
func (h *FileHandler) Avatar(c *gin.Context) {
	agent, ok := authenticate(c, h.auth)
	if !ok {
		return
	}

	const (
		maxAvatarSizeB   = 5 * 1024 * 1024
		maxAvatarMemForm = 8 * 1024 * 1024
	)

	if err := c.Request.ParseMultipartForm(maxAvatarMemForm); err != nil {
		c.JSON(http.StatusBadRequest, agentError("invalid multipart form"))
		return
	}
	if c.Request.MultipartForm != nil {
		defer func() {
			_ = c.Request.MultipartForm.RemoveAll()
		}()
	}

	files := c.Request.MultipartForm.File["file"]
	if len(files) == 0 {
		c.JSON(http.StatusBadRequest, agentError("file is required"))
		return
	}

	header := files[0]
	opened, err := header.Open()
	if err != nil {
		c.JSON(http.StatusBadRequest, agentError("invalid file upload"))
		return
	}
	defer func() { _ = opened.Close() }()

	limited := io.LimitReader(opened, maxAvatarSizeB+1)
	blob, err := io.ReadAll(limited)
	if err != nil {
		c.JSON(http.StatusBadRequest, agentError("failed to read uploaded file"))
		return
	}
	if len(blob) > maxAvatarSizeB {
		c.JSON(http.StatusBadRequest, agentError("file too large (max 5MB)"))
		return
	}

	contentType := strings.TrimSpace(header.Header.Get("Content-Type"))
	if contentType == "" {
		contentType = http.DetectContentType(blob)
	}
	if !strings.HasPrefix(contentType, "image/") {
		c.JSON(http.StatusBadRequest, agentError("only image files are allowed"))
		return
	}

	avatarID, err := h.files.UploadAgentAvatar(c.Request.Context(), agent.WorkspaceID, agent.AgentID, createAttachmentParams(header.Filename, contentType, blob))
	if err != nil {
		c.JSON(HTTPStatus(err), agentError(err.Error()))
		return
	}

	avatarURL := attachment.VersionedAgentAvatarURL(agent.WorkspaceID, agent.AgentID, avatarID)

	if err := h.agents.UpdateAgentAvatarURL(c.Request.Context(), agent.WorkspaceID, agent.AgentID, avatarURL); err != nil {
		c.JSON(HTTPStatus(err), agentError(err.Error()))
		return
	}

	c.JSON(http.StatusOK, avatarUploadCompatibilityResponse(dto.AgentAvatarUploadResponse{
		ID:        avatarID,
		AvatarURL: avatarURL,
	}))
}
