// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/tag/assign.go
package tag

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"

	"github.com/gocql/gocql"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config/constants"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
	svc_tag "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/tag"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)

type AssignTagRequest struct {
	EntityID   string `json:"entity_id"`
	EntityType string `json:"entity_type"` // "collection" or "file"
}

type AssignTagHTTPHandler struct {
	config     *config.Configuration
	logger     *zap.Logger
	service    *svc_tag.TagService
	middleware middleware.Middleware
}

func NewAssignTagHTTPHandler(
	config *config.Configuration,
	logger *zap.Logger,
	service *svc_tag.TagService,
	middleware middleware.Middleware,
) *AssignTagHTTPHandler {
	logger = logger.Named("AssignTagHTTPHandler")
	return &AssignTagHTTPHandler{
		config:     config,
		logger:     logger,
		service:    service,
		middleware: middleware,
	}
}

func (*AssignTagHTTPHandler) Pattern() string {
	return "POST /api/v1/tags/{id}/assign"
}

func (h *AssignTagHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	h.middleware.Attach(h.Execute)(w, req)
}

func (h *AssignTagHTTPHandler) unmarshalRequest(
	ctx context.Context,
	r *http.Request,
) (*AssignTagRequest, error) {
	var requestData AssignTagRequest

	defer r.Body.Close()

	var rawJSON bytes.Buffer
	teeReader := io.TeeReader(r.Body, &rawJSON)

	err := json.NewDecoder(teeReader).Decode(&requestData)
	if err != nil {
		h.logger.Error("Failed to decode assign tag request",
			zap.Error(err),
			zap.String("json", rawJSON.String()),
		)
		return nil, httperror.NewBadRequestError("Invalid request payload")
	}

	return &requestData, nil
}

func (h *AssignTagHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	// Get user ID from JWT context
	userID, ok := ctx.Value(constants.SessionUserID).(gocql.UUID)
	if !ok {
		h.logger.Error("Failed to get user ID from context")
		httperror.ResponseError(w, httperror.NewUnauthorizedError("User not authenticated"))
		return
	}

	// Get tag ID from path
	tagIDStr := r.PathValue("id")
	if tagIDStr == "" {
		httperror.ResponseError(w, httperror.NewBadRequestError("Tag ID is required"))
		return
	}

	tagID, err := gocql.ParseUUID(tagIDStr)
	if err != nil {
		h.logger.Error("Invalid tag ID", zap.Error(err), zap.String("id", tagIDStr))
		httperror.ResponseError(w, httperror.NewBadRequestError("Invalid tag ID format"))
		return
	}

	// Parse request
	req, err := h.unmarshalRequest(ctx, r)
	if err != nil {
		h.logger.Error("Failed to unmarshal request", zap.Error(err))
		httperror.ResponseError(w, err)
		return
	}

	// Parse entity ID
	entityID, err := gocql.ParseUUID(req.EntityID)
	if err != nil {
		h.logger.Error("Invalid entity ID", zap.Error(err), zap.String("entity_id", req.EntityID))
		httperror.ResponseError(w, httperror.NewBadRequestError("Invalid entity ID format"))
		return
	}

	// Assign tag
	if err := h.service.AssignTag(ctx, userID, tagID, entityID, req.EntityType); err != nil {
		h.logger.Error("Failed to assign tag",
			zap.Error(err),
			zap.String("tag_id", tagIDStr),
			zap.String("entity_id", req.EntityID),
			zap.String("entity_type", req.EntityType),
		)
		httperror.ResponseError(w, httperror.NewInternalServerError("Failed to assign tag"))
		return
	}

	// Return response
	w.WriteHeader(http.StatusNoContent)
}
