package channel

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"strconv"
	"strings"
	"time"

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

const (
	defaultChannelAttachmentListLimit = 30
	maxChannelAttachmentListLimit     = 100
)

type ChannelAttachmentStore interface {
	ListChannelAttachmentsForUser(ctx context.Context, userID string, workspaceID string, channelID string, options message.ChannelAttachmentListOptions) (message.ChannelAttachmentPage, error)
	ListThreadAttachmentsForUser(ctx context.Context, userID string, workspaceID string, threadChannelID string, options message.ChannelAttachmentListOptions) (message.ChannelAttachmentPage, error)
}

type ChannelAttachmentsHandler struct {
	appStore ChannelAttachmentStore
}

// List handles GET /api/v1/workspaces/:workspaceId/channels/:channelId/attachments.
// It lists attachments visible to the current human in the channel and,
// unless explicitly excluded, its reply threads.
func (h *ChannelAttachmentsHandler) List(r api.WorkspaceScopedRequest) (api.Response, error) {
	if h.appStore == nil {
		return api.Response{}, api.NewHTTPError(http.StatusNotImplemented, "channel attachments are not configured")
	}
	channelID := strings.TrimSpace(r.Gin.Param("channelId"))
	if channelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "channelId is required")
	}
	options, err := parseChannelAttachmentListOptions(r)
	if err != nil {
		return api.Response{}, err
	}
	page, err := h.appStore.ListChannelAttachmentsForUser(r.Context(), r.User.User.ID, r.WorkspaceID(), channelID, options)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToChannelAttachmentPage(page, encodeChannelAttachmentCursor)), nil
}

// ListThread handles GET /api/v1/workspaces/:workspaceId/threads/:threadChannelId/attachments.
// It lists attachments from messages in one reply thread only.
func (h *ChannelAttachmentsHandler) ListThread(r api.WorkspaceScopedRequest) (api.Response, error) {
	if h.appStore == nil {
		return api.Response{}, api.NewHTTPError(http.StatusNotImplemented, "thread attachments are not configured")
	}
	threadChannelID := strings.TrimSpace(r.Gin.Param("threadChannelId"))
	if threadChannelID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "threadChannelId is required")
	}
	options, err := parseChannelAttachmentListOptions(r)
	if err != nil {
		return api.Response{}, err
	}
	options.IncludeThreads = false
	page, err := h.appStore.ListThreadAttachmentsForUser(r.Context(), r.User.User.ID, r.WorkspaceID(), threadChannelID, options)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToChannelAttachmentPage(page, encodeChannelAttachmentCursor)), nil
}

func parseChannelAttachmentListOptions(r api.WorkspaceScopedRequest) (message.ChannelAttachmentListOptions, error) {
	limit, err := parseChannelAttachmentListLimit(r.Gin.Query("limit"))
	if err != nil {
		return message.ChannelAttachmentListOptions{}, err
	}
	cursor, err := decodeChannelAttachmentCursor(r.Gin.Query("cursor"))
	if err != nil {
		return message.ChannelAttachmentListOptions{}, err
	}
	dateFrom, err := parseChannelAttachmentTimeQuery(r.Gin.Query("dateFrom"), "dateFrom")
	if err != nil {
		return message.ChannelAttachmentListOptions{}, err
	}
	dateTo, err := parseChannelAttachmentTimeQuery(r.Gin.Query("dateTo"), "dateTo")
	if err != nil {
		return message.ChannelAttachmentListOptions{}, err
	}
	return message.ChannelAttachmentListOptions{
		Kind:           message.ChannelAttachmentKind(strings.TrimSpace(r.Gin.Query("kind"))),
		Query:          strings.TrimSpace(r.Gin.Query("q")),
		FromMemberID:   strings.TrimSpace(r.Gin.Query("fromMemberId")),
		DateFrom:       dateFrom,
		DateTo:         dateTo,
		IncludeThreads: parseChannelAttachmentIncludeThreads(r.Gin.Query("includeThreads")),
		Cursor:         cursor,
		Limit:          limit,
	}, nil
}

func parseChannelAttachmentListLimit(raw string) (int, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return defaultChannelAttachmentListLimit, nil
	}
	limit, err := strconv.Atoi(trimmed)
	if err != nil || limit <= 0 {
		return 0, api.NewHTTPError(http.StatusBadRequest, "limit must be a positive integer")
	}
	if limit > maxChannelAttachmentListLimit {
		return maxChannelAttachmentListLimit, nil
	}
	return limit, nil
}

func parseChannelAttachmentIncludeThreads(raw string) bool {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "false", "0", "no":
		return false
	default:
		return true
	}
}

func parseChannelAttachmentTimeQuery(raw string, name string) (*time.Time, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil, nil
	}
	value, err := time.Parse(time.RFC3339Nano, trimmed)
	if err != nil {
		return nil, api.NewHTTPError(http.StatusBadRequest, name+" must be an RFC3339 timestamp")
	}
	value = value.UTC()
	return &value, nil
}

type channelAttachmentCursorToken struct {
	CreatedAt       time.Time `json:"createdAt"`
	MessageID       string    `json:"messageId"`
	ResourceOrdinal int       `json:"resourceOrdinal"`
}

func encodeChannelAttachmentCursor(cursor message.ChannelAttachmentCursor) string {
	if cursor.IsZero() || cursor.ResourceOrdinal <= 0 {
		return ""
	}
	payload, err := json.Marshal(channelAttachmentCursorToken{
		CreatedAt:       cursor.CreatedAt.UTC(),
		MessageID:       strings.TrimSpace(cursor.MessageID),
		ResourceOrdinal: cursor.ResourceOrdinal,
	})
	if err != nil {
		return ""
	}
	return base64.RawURLEncoding.EncodeToString(payload)
}

func decodeChannelAttachmentCursor(token string) (message.ChannelAttachmentCursor, error) {
	trimmed := strings.TrimSpace(token)
	if trimmed == "" {
		return message.ChannelAttachmentCursor{}, nil
	}
	payload, err := base64.RawURLEncoding.DecodeString(trimmed)
	if err != nil {
		return message.ChannelAttachmentCursor{}, api.NewHTTPError(http.StatusBadRequest, "invalid attachment cursor")
	}
	var decoded channelAttachmentCursorToken
	if err := json.Unmarshal(payload, &decoded); err != nil {
		return message.ChannelAttachmentCursor{}, api.NewHTTPError(http.StatusBadRequest, "invalid attachment cursor")
	}
	messageID := strings.TrimSpace(decoded.MessageID)
	if decoded.CreatedAt.IsZero() || messageID == "" || decoded.ResourceOrdinal <= 0 {
		return message.ChannelAttachmentCursor{}, api.NewHTTPError(http.StatusBadRequest, "invalid attachment cursor")
	}
	return message.ChannelAttachmentCursor{CreatedAt: decoded.CreatedAt.UTC(), MessageID: messageID, ResourceOrdinal: decoded.ResourceOrdinal}, nil
}
