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

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

	"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"
	dom_crypto "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/crypto"
	dom_tag "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/tag"
	"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"
)

// UpdateTagRequest contains encrypted tag data from the client (E2EE)
type UpdateTagRequest struct {
	ID                  string              `json:"id"`
	UserID              string              `json:"user_id"`
	EncryptedName       string              `json:"encrypted_name"`
	EncryptedColor      string              `json:"encrypted_color"`
	EncryptedTagKey     *EncryptedTagKeyDTO `json:"encrypted_tag_key"`
	CreatedAt           string              `json:"created_at"`
	ModifiedAt          string              `json:"modified_at"`
	Version             uint64              `json:"version"`
	State               string              `json:"state"`
}

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

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

func (*UpdateTagHTTPHandler) Pattern() string {
	return "PUT /api/v1/tags/{id}"
}

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

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

	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 update tag request",
			zap.Error(err),
			zap.String("json", rawJSON.String()),
		)
		return nil, httperror.NewBadRequestError("Invalid request payload")
	}

	return &requestData, nil
}

func (h *UpdateTagHTTPHandler) 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
	idStr := r.PathValue("id")
	if idStr == "" {
		httperror.ResponseError(w, httperror.NewBadRequestError("Tag ID is required"))
		return
	}

	tagID, err := gocql.ParseUUID(idStr)
	if err != nil {
		h.logger.Error("Invalid tag ID", zap.Error(err), zap.String("id", idStr))
		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 timestamps
	createdAt, err := time.Parse(time.RFC3339, req.CreatedAt)
	if err != nil {
		h.logger.Error("Invalid created_at timestamp", zap.Error(err))
		httperror.ResponseError(w, httperror.NewBadRequestError("Invalid created_at timestamp"))
		return
	}

	modifiedAt, err := time.Parse(time.RFC3339, req.ModifiedAt)
	if err != nil {
		h.logger.Error("Invalid modified_at timestamp", zap.Error(err))
		httperror.ResponseError(w, httperror.NewBadRequestError("Invalid modified_at timestamp"))
		return
	}

	// Decode encrypted tag key
	var encryptedTagKey *dom_crypto.EncryptedTagKey
	if req.EncryptedTagKey != nil {
		// Decode ciphertext from URL-safe base64
		ciphertext, err := base64.RawURLEncoding.DecodeString(req.EncryptedTagKey.Ciphertext)
		if err != nil {
			// Fallback to standard encoding
			ciphertext, err = base64.StdEncoding.DecodeString(req.EncryptedTagKey.Ciphertext)
			if err != nil {
				h.logger.Error("Failed to decode tag key ciphertext", zap.Error(err))
				httperror.ResponseError(w, httperror.NewBadRequestError("Invalid tag key ciphertext"))
				return
			}
		}

		// Decode nonce from URL-safe base64
		nonce, err := base64.RawURLEncoding.DecodeString(req.EncryptedTagKey.Nonce)
		if err != nil {
			// Fallback to standard encoding
			nonce, err = base64.StdEncoding.DecodeString(req.EncryptedTagKey.Nonce)
			if err != nil {
				h.logger.Error("Failed to decode tag key nonce", zap.Error(err))
				httperror.ResponseError(w, httperror.NewBadRequestError("Invalid tag key nonce"))
				return
			}
		}

		encryptedTagKey = &dom_crypto.EncryptedTagKey{
			Ciphertext: ciphertext,
			Nonce:      nonce,
			KeyVersion: 1,
		}
	}

	// Create tag domain object
	tag := &dom_tag.Tag{
		ID:              tagID,
		UserID:          userID,
		EncryptedName:   req.EncryptedName,
		EncryptedColor:  req.EncryptedColor,
		EncryptedTagKey: encryptedTagKey,
		CreatedAt:       createdAt,
		ModifiedAt:      modifiedAt,
		Version:         req.Version,
		State:           req.State,
	}

	// Update tag
	err = h.service.UpdateTag(ctx, tag)
	if err != nil {
		h.logger.Error("Failed to update tag",
			zap.Error(err),
			zap.String("tag_id", tagID.String()),
		)
		httperror.ResponseError(w, httperror.NewInternalServerError("Failed to update tag"))
		return
	}

	// Return response
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(tag)
}
