package http

import (
	"context"
	"encoding/hex"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/rwadurian/mpc-system/pkg/logger"
	grpcclient "github.com/rwadurian/mpc-system/services/account/adapters/output/grpc"
	"github.com/rwadurian/mpc-system/services/account/application/ports"
	"github.com/rwadurian/mpc-system/services/account/application/use_cases"
	"github.com/rwadurian/mpc-system/services/account/domain/entities"
	"github.com/rwadurian/mpc-system/services/account/domain/repositories"
	"github.com/rwadurian/mpc-system/services/account/domain/value_objects"
	"go.uber.org/zap"
)

// AccountHTTPHandler handles HTTP requests for accounts
type AccountHTTPHandler struct {
	accountRepo              repositories.AccountRepository
	sessionEventRepo         repositories.SessionEventRepository
	createAccountUC          *use_cases.CreateAccountUseCase
	getAccountUC             *use_cases.GetAccountUseCase
	updateAccountUC          *use_cases.UpdateAccountUseCase
	listAccountsUC           *use_cases.ListAccountsUseCase
	getAccountSharesUC       *use_cases.GetAccountSharesUseCase
	deactivateShareUC        *use_cases.DeactivateShareUseCase
	loginUC                  *use_cases.LoginUseCase
	refreshTokenUC           *use_cases.RefreshTokenUseCase
	generateChallengeUC      *use_cases.GenerateChallengeUseCase
	initiateRecoveryUC       *use_cases.InitiateRecoveryUseCase
	completeRecoveryUC       *use_cases.CompleteRecoveryUseCase
	getRecoveryStatusUC      *use_cases.GetRecoveryStatusUseCase
	cancelRecoveryUC         *use_cases.CancelRecoveryUseCase
	sessionCoordinatorClient *grpcclient.SessionCoordinatorClient
}

// NewAccountHTTPHandler creates a new AccountHTTPHandler
func NewAccountHTTPHandler(
	accountRepo repositories.AccountRepository,
	sessionEventRepo repositories.SessionEventRepository,
	createAccountUC *use_cases.CreateAccountUseCase,
	getAccountUC *use_cases.GetAccountUseCase,
	updateAccountUC *use_cases.UpdateAccountUseCase,
	listAccountsUC *use_cases.ListAccountsUseCase,
	getAccountSharesUC *use_cases.GetAccountSharesUseCase,
	deactivateShareUC *use_cases.DeactivateShareUseCase,
	loginUC *use_cases.LoginUseCase,
	refreshTokenUC *use_cases.RefreshTokenUseCase,
	generateChallengeUC *use_cases.GenerateChallengeUseCase,
	initiateRecoveryUC *use_cases.InitiateRecoveryUseCase,
	completeRecoveryUC *use_cases.CompleteRecoveryUseCase,
	getRecoveryStatusUC *use_cases.GetRecoveryStatusUseCase,
	cancelRecoveryUC *use_cases.CancelRecoveryUseCase,
	sessionCoordinatorClient *grpcclient.SessionCoordinatorClient,
) *AccountHTTPHandler {
	return &AccountHTTPHandler{
		accountRepo:              accountRepo,
		sessionEventRepo:         sessionEventRepo,
		createAccountUC:          createAccountUC,
		getAccountUC:             getAccountUC,
		updateAccountUC:          updateAccountUC,
		listAccountsUC:           listAccountsUC,
		getAccountSharesUC:       getAccountSharesUC,
		deactivateShareUC:        deactivateShareUC,
		loginUC:                  loginUC,
		refreshTokenUC:           refreshTokenUC,
		generateChallengeUC:      generateChallengeUC,
		initiateRecoveryUC:       initiateRecoveryUC,
		completeRecoveryUC:       completeRecoveryUC,
		getRecoveryStatusUC:      getRecoveryStatusUC,
		cancelRecoveryUC:         cancelRecoveryUC,
		sessionCoordinatorClient: sessionCoordinatorClient,
	}
}

// RegisterRoutes registers HTTP routes
func (h *AccountHTTPHandler) RegisterRoutes(router *gin.RouterGroup) {
	accounts := router.Group("/accounts")
	{
		accounts.POST("", h.CreateAccount)
		accounts.POST("/from-keygen", h.CreateAccountFromKeygen)
		accounts.GET("", h.ListAccounts)
		accounts.GET("/:id", h.GetAccount)
		accounts.PUT("/:id", h.UpdateAccount)
		accounts.GET("/:id/shares", h.GetAccountShares)
		accounts.DELETE("/:id/shares/:shareId", h.DeactivateShare)
		// Signing parties configuration (use username as path parameter)
		accounts.POST("/by-username/:username/signing-config", h.SetSigningParties)
		accounts.PUT("/by-username/:username/signing-config", h.UpdateSigningParties)
		accounts.DELETE("/by-username/:username/signing-config", h.ClearSigningParties)
		accounts.GET("/by-username/:username/signing-config", h.GetSigningParties)
	}

	auth := router.Group("/auth")
	{
		auth.POST("/challenge", h.GenerateChallenge)
		auth.POST("/login", h.Login)
		auth.POST("/refresh", h.RefreshToken)
	}

	recovery := router.Group("/recovery")
	{
		recovery.POST("", h.InitiateRecovery)
		recovery.GET("/:id", h.GetRecoveryStatus)
		recovery.POST("/:id/complete", h.CompleteRecovery)
		recovery.POST("/:id/cancel", h.CancelRecovery)
	}

	// MPC session management
	mpc := router.Group("/mpc")
	{
		mpc.POST("/keygen", h.CreateKeygenSession)
		mpc.POST("/sign", h.CreateSigningSession)
		mpc.GET("/sessions/:id", h.GetSessionStatus)
	}
}

// CreateAccountRequest represents the request for creating an account
type CreateAccountRequest struct {
	Username        string        `json:"username" binding:"required"`
	Email           string        `json:"email" binding:"omitempty,email"`
	Phone           *string       `json:"phone"`
	PublicKey       string        `json:"publicKey" binding:"required"`
	KeygenSessionID string        `json:"keygenSessionId" binding:"required"`
	ThresholdN      int           `json:"thresholdN" binding:"required,min=1"`
	ThresholdT      int           `json:"thresholdT" binding:"required,min=1"`
	Shares          []ShareInput  `json:"shares" binding:"required,min=1"`
}

// ShareInput represents a share in the request
type ShareInput struct {
	ShareType  string  `json:"shareType" binding:"required"`
	PartyID    string  `json:"partyId" binding:"required"`
	PartyIndex int     `json:"partyIndex" binding:"required,min=0"`
	DeviceType *string `json:"deviceType"`
	DeviceID   *string `json:"deviceId"`
}

// CreateAccount handles account creation
func (h *AccountHTTPHandler) CreateAccount(c *gin.Context) {
	var req CreateAccountRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	keygenSessionID, err := uuid.Parse(req.KeygenSessionID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid keygen session ID"})
		return
	}

	shares := make([]ports.ShareInput, len(req.Shares))
	for i, s := range req.Shares {
		shares[i] = ports.ShareInput{
			ShareType:  value_objects.ShareType(s.ShareType),
			PartyID:    s.PartyID,
			PartyIndex: s.PartyIndex,
			DeviceType: s.DeviceType,
			DeviceID:   s.DeviceID,
		}
	}

	// Decode hex-encoded public key
	publicKeyBytes, err := hex.DecodeString(req.PublicKey)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid public key format"})
		return
	}

	output, err := h.createAccountUC.Execute(c.Request.Context(), ports.CreateAccountInput{
		Username:        req.Username,
		Email:           req.Email,
		Phone:           req.Phone,
		PublicKey:       publicKeyBytes,
		KeygenSessionID: keygenSessionID,
		ThresholdN:      req.ThresholdN,
		ThresholdT:      req.ThresholdT,
		Shares:          shares,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"account": output.Account,
		"shares":  output.Shares,
	})
}

// GetAccount handles getting account by ID
func (h *AccountHTTPHandler) GetAccount(c *gin.Context) {
	idStr := c.Param("id")
	accountID, err := value_objects.AccountIDFromString(idStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"})
		return
	}

	output, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{
		AccountID: &accountID,
	})
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"account": output.Account,
		"shares":  output.Shares,
	})
}

// UpdateAccountRequest represents the request for updating an account
type UpdateAccountRequest struct {
	Phone *string `json:"phone"`
}

// UpdateAccount handles account updates
func (h *AccountHTTPHandler) UpdateAccount(c *gin.Context) {
	idStr := c.Param("id")
	accountID, err := value_objects.AccountIDFromString(idStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"})
		return
	}

	var req UpdateAccountRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	output, err := h.updateAccountUC.Execute(c.Request.Context(), ports.UpdateAccountInput{
		AccountID: accountID,
		Phone:     req.Phone,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, output.Account)
}

// ListAccounts handles listing accounts
func (h *AccountHTTPHandler) ListAccounts(c *gin.Context) {
	var offset, limit int
	if o := c.Query("offset"); o != "" {
		// Parse offset
	}
	if l := c.Query("limit"); l != "" {
		// Parse limit
	}

	output, err := h.listAccountsUC.Execute(c.Request.Context(), use_cases.ListAccountsInput{
		Offset: offset,
		Limit:  limit,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"accounts": output.Accounts,
		"total":    output.Total,
	})
}

// GetAccountShares handles getting account shares
func (h *AccountHTTPHandler) GetAccountShares(c *gin.Context) {
	idStr := c.Param("id")
	accountID, err := value_objects.AccountIDFromString(idStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"})
		return
	}

	output, err := h.getAccountSharesUC.Execute(c.Request.Context(), accountID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"shares": output.Shares,
	})
}

// DeactivateShare handles share deactivation
func (h *AccountHTTPHandler) DeactivateShare(c *gin.Context) {
	idStr := c.Param("id")
	accountID, err := value_objects.AccountIDFromString(idStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"})
		return
	}

	shareID := c.Param("shareId")

	err = h.deactivateShareUC.Execute(c.Request.Context(), ports.DeactivateShareInput{
		AccountID: accountID,
		ShareID:   shareID,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "share deactivated"})
}

// GenerateChallengeRequest represents the request for generating a challenge
type GenerateChallengeRequest struct {
	Username string `json:"username" binding:"required"`
}

// GenerateChallenge handles challenge generation
func (h *AccountHTTPHandler) GenerateChallenge(c *gin.Context) {
	var req GenerateChallengeRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	output, err := h.generateChallengeUC.Execute(c.Request.Context(), use_cases.GenerateChallengeInput{
		Username: req.Username,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"challengeId": output.ChallengeID,
		"challenge":   hex.EncodeToString(output.Challenge),
		"expiresAt":   output.ExpiresAt,
	})
}

// LoginRequest represents the request for login
type LoginRequest struct {
	Username  string `json:"username" binding:"required"`
	Challenge string `json:"challenge" binding:"required"`
	Signature string `json:"signature" binding:"required"`
}

// Login handles user login
func (h *AccountHTTPHandler) Login(c *gin.Context) {
	var req LoginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Decode hex-encoded challenge and signature
	// Return 401 Unauthorized for invalid formats (treated as invalid credentials)
	challengeBytes, err := hex.DecodeString(req.Challenge)
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
		return
	}

	signatureBytes, err := hex.DecodeString(req.Signature)
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
		return
	}

	output, err := h.loginUC.Execute(c.Request.Context(), ports.LoginInput{
		Username:  req.Username,
		Challenge: challengeBytes,
		Signature: signatureBytes,
	})
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"account":      output.Account,
		"accessToken":  output.AccessToken,
		"refreshToken": output.RefreshToken,
	})
}

// RefreshTokenRequest represents the request for refreshing tokens
type RefreshTokenRequest struct {
	RefreshToken string `json:"refreshToken" binding:"required"`
}

// RefreshToken handles token refresh
func (h *AccountHTTPHandler) RefreshToken(c *gin.Context) {
	var req RefreshTokenRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	output, err := h.refreshTokenUC.Execute(c.Request.Context(), use_cases.RefreshTokenInput{
		RefreshToken: req.RefreshToken,
	})
	if err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"accessToken":  output.AccessToken,
		"refreshToken": output.RefreshToken,
	})
}

// InitiateRecoveryRequest represents the request for initiating recovery
type InitiateRecoveryRequest struct {
	AccountID    string  `json:"accountId" binding:"required"`
	RecoveryType string  `json:"recoveryType" binding:"required"`
	OldShareType *string `json:"oldShareType"`
}

// InitiateRecovery handles recovery initiation
func (h *AccountHTTPHandler) InitiateRecovery(c *gin.Context) {
	var req InitiateRecoveryRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	accountID, err := value_objects.AccountIDFromString(req.AccountID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"})
		return
	}

	input := ports.InitiateRecoveryInput{
		AccountID:    accountID,
		RecoveryType: value_objects.RecoveryType(req.RecoveryType),
	}

	if req.OldShareType != nil {
		st := value_objects.ShareType(*req.OldShareType)
		input.OldShareType = &st
	}

	output, err := h.initiateRecoveryUC.Execute(c.Request.Context(), input)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"recoverySession": output.RecoverySession,
	})
}

// GetRecoveryStatus handles getting recovery status
func (h *AccountHTTPHandler) GetRecoveryStatus(c *gin.Context) {
	id := c.Param("id")

	output, err := h.getRecoveryStatusUC.Execute(c.Request.Context(), use_cases.GetRecoveryStatusInput{
		RecoverySessionID: id,
	})
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, output.RecoverySession)
}

// CompleteRecoveryRequest represents the request for completing recovery
type CompleteRecoveryRequest struct {
	NewPublicKey       string       `json:"newPublicKey" binding:"required"`
	NewKeygenSessionID string       `json:"newKeygenSessionId" binding:"required"`
	NewShares          []ShareInput `json:"newShares" binding:"required,min=1"`
}

// CompleteRecovery handles recovery completion
func (h *AccountHTTPHandler) CompleteRecovery(c *gin.Context) {
	id := c.Param("id")

	var req CompleteRecoveryRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	newKeygenSessionID, err := uuid.Parse(req.NewKeygenSessionID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid keygen session ID"})
		return
	}

	newShares := make([]ports.ShareInput, len(req.NewShares))
	for i, s := range req.NewShares {
		newShares[i] = ports.ShareInput{
			ShareType:  value_objects.ShareType(s.ShareType),
			PartyID:    s.PartyID,
			PartyIndex: s.PartyIndex,
			DeviceType: s.DeviceType,
			DeviceID:   s.DeviceID,
		}
	}

	// Decode hex-encoded public key
	newPublicKeyBytes, err := hex.DecodeString(req.NewPublicKey)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid public key format"})
		return
	}

	output, err := h.completeRecoveryUC.Execute(c.Request.Context(), ports.CompleteRecoveryInput{
		RecoverySessionID:  id,
		NewPublicKey:       newPublicKeyBytes,
		NewKeygenSessionID: newKeygenSessionID,
		NewShares:          newShares,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, output.Account)
}

// CancelRecovery handles recovery cancellation
func (h *AccountHTTPHandler) CancelRecovery(c *gin.Context) {
	id := c.Param("id")

	err := h.cancelRecoveryUC.Execute(c.Request.Context(), use_cases.CancelRecoveryInput{
		RecoverySessionID: id,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "recovery cancelled"})
}

// ============================================
// MPC Session Management Endpoints
// ============================================

// CreateKeygenSessionRequest represents the request for creating a keygen session
// Coordinator will automatically select parties from registered pool
type CreateKeygenSessionRequest struct {
	Username        string `json:"username" binding:"required"`          // Username - the unique identifier for all relationships
	ThresholdN      int    `json:"threshold_n" binding:"required,min=2"` // Total number of parties (e.g., 3)
	ThresholdT      int    `json:"threshold_t" binding:"required,min=1"` // Threshold for signing (e.g., 2)
	RequireDelegate bool   `json:"require_delegate"`                     // If true, one party will be delegate (returns share to user)
}

// CreateKeygenSession handles creating a new keygen session
// Parties are automatically selected by Coordinator from registered pool
func (h *AccountHTTPHandler) CreateKeygenSession(c *gin.Context) {
	var req CreateKeygenSessionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Validate threshold
	if req.ThresholdT > req.ThresholdN {
		c.JSON(http.StatusBadRequest, gin.H{"error": "threshold_t cannot be greater than threshold_n"})
		return
	}

	// Check if username already exists (keygen should be for new users)
	exists, err := h.accountRepo.ExistsByUsername(c.Request.Context(), req.Username)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to check username"})
		return
	}
	if exists {
		c.JSON(http.StatusConflict, gin.H{"error": "username already exists, use sign API instead"})
		return
	}

	// Call session coordinator via gRPC (no participants - coordinator selects automatically)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	logger.Info("Calling CreateKeygenSession via gRPC (auto party selection)",
		zap.String("username", req.Username),
		zap.Int("threshold_n", req.ThresholdN),
		zap.Int("threshold_t", req.ThresholdT),
		zap.Bool("require_delegate", req.RequireDelegate))

	// Calculate party composition based on require_delegate
	var persistentCount, delegateCount int
	if req.RequireDelegate {
		// One delegate party, rest are persistent
		delegateCount = 1
		persistentCount = req.ThresholdN - 1
	} else {
		// All persistent parties
		persistentCount = req.ThresholdN
		delegateCount = 0
	}

	resp, err := h.sessionCoordinatorClient.CreateKeygenSessionAuto(
		ctx,
		int32(req.ThresholdN),
		int32(req.ThresholdT),
		int32(persistentCount),
		int32(delegateCount),
		600, // 10 minutes expiry
	)

	if err != nil {
		logger.Error("gRPC CreateKeygenSession failed", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	logger.Info("gRPC CreateKeygenSession succeeded",
		zap.String("session_id", resp.SessionID),
		zap.Int("num_parties", len(resp.SelectedParties)))

	// Record session_created event
	sessionID, _ := uuid.Parse(resp.SessionID)
	event := repositories.NewSessionEvent(
		sessionID,
		req.Username,
		repositories.EventSessionCreated,
		repositories.SessionTypeKeygen,
	).WithThreshold(req.ThresholdN, req.ThresholdT).WithMetadata(map[string]interface{}{
		"selected_parties":  resp.SelectedParties,
		"delegate_party":    resp.DelegateParty,
		"require_delegate":  req.RequireDelegate,
		"persistent_count":  persistentCount,
		"delegate_count":    delegateCount,
	})
	if err := h.sessionEventRepo.Create(c.Request.Context(), event); err != nil {
		logger.Error("Failed to record session event", zap.Error(err))
		// Don't fail the request, just log the error
	}

	// Return response with selected parties info
	c.JSON(http.StatusCreated, gin.H{
		"session_id":       resp.SessionID,
		"session_type":     "keygen",
		"username":         req.Username, // Include username in response for reference
		"threshold_n":      req.ThresholdN,
		"threshold_t":      req.ThresholdT,
		"selected_parties": resp.SelectedParties,
		"delegate_party":   resp.DelegateParty, // The party that will return share to user
		"status":           "created",
	})
}

// CreateSigningSessionRequest represents the request for creating a signing session
// Coordinator will automatically select parties based on account's registered shares
type CreateSigningSessionRequest struct {
	Username    string `json:"username" binding:"required"`     // Username - the unique identifier for all relationships
	MessageHash string `json:"message_hash" binding:"required"` // SHA-256 hash to sign (hex encoded)
	UserShare   string `json:"user_share"`                      // Required if account has delegate share: user's encrypted share (hex)
}

// CreateSigningSession handles creating a new signing session
// Parties are automatically selected based on the account's registered shares
func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
	var req CreateSigningSessionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Decode message hash
	messageHash, err := hex.DecodeString(req.MessageHash)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid message_hash format (expected hex)"})
		return
	}

	if len(messageHash) != 32 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "message_hash must be 32 bytes (SHA-256)"})
		return
	}

	// Get account by username to verify it exists and get share info
	accountOutput, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{
		Username: &req.Username,
	})
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "account not found for username: " + req.Username})
		return
	}

	// Build a map of active shares for validation
	activeSharesMap := make(map[string]*entities.AccountShare)
	var allActivePartyIDs []string
	var delegateShare *entities.AccountShare
	for _, share := range accountOutput.Shares {
		if share.IsActive {
			activeSharesMap[share.PartyID] = share
			allActivePartyIDs = append(allActivePartyIDs, share.PartyID)
			// Check if this is a delegate share
			if share.ShareType == value_objects.ShareTypeDelegate {
				delegateShare = share
			}
		}
	}

	// Determine which parties to use for signing
	var signingParties []grpcclient.SigningPartyInfo
	var partyIDs []string // For logging and validation
	if accountOutput.Account.HasSigningPartiesConfig() {
		// Use configured signing parties
		configuredParties := accountOutput.Account.GetSigningParties()

		// Validate all configured parties are still active and build signing party info
		for _, partyID := range configuredParties {
			share, exists := activeSharesMap[partyID]
			if !exists {
				c.JSON(http.StatusBadRequest, gin.H{
					"error":    "configured signing party is no longer active",
					"party_id": partyID,
					"hint":     "update signing-config with active parties",
				})
				return
			}
			signingParties = append(signingParties, grpcclient.SigningPartyInfo{
				PartyID:    partyID,
				PartyIndex: int32(share.PartyIndex),
			})
			partyIDs = append(partyIDs, partyID)
		}

		logger.Info("Using configured signing parties",
			zap.String("username", req.Username),
			zap.Strings("configured_parties", partyIDs))
	} else {
		// For threshold signing, select minimum required parties (threshold_t + 1)
		// TSS threshold semantics: for threshold t, we need t+1 signers
		// For 2-of-3: t=1, so we need t+1=2 parties to sign
		requiredParties := accountOutput.Account.ThresholdT + 1
		if len(allActivePartyIDs) < requiredParties {
			c.JSON(http.StatusBadRequest, gin.H{
				"error":            "insufficient active parties for signing",
				"required":         requiredParties,
				"available":        len(allActivePartyIDs),
			})
			return
		}

		// Select first 'threshold_t + 1' parties with their original PartyIndex
		for i := 0; i < requiredParties; i++ {
			partyID := allActivePartyIDs[i]
			share := activeSharesMap[partyID]
			signingParties = append(signingParties, grpcclient.SigningPartyInfo{
				PartyID:    partyID,
				PartyIndex: int32(share.PartyIndex),
			})
			partyIDs = append(partyIDs, partyID)
		}

		logger.Info("Using minimum required parties for threshold signing",
			zap.String("username", req.Username),
			zap.Int("threshold_t", accountOutput.Account.ThresholdT),
			zap.Int("required_signers", requiredParties),
			zap.Int("total_active", len(allActivePartyIDs)),
			zap.Strings("selected_parties", partyIDs))
	}

	// Check if any of the selected parties is a delegate
	// (delegate share might not be in the configured signing parties)
	var selectedDelegateShare *entities.AccountShare
	for _, partyID := range partyIDs {
		if share := activeSharesMap[partyID]; share != nil && share.ShareType == value_objects.ShareTypeDelegate {
			selectedDelegateShare = share
			break
		}
	}

	// If selected parties include delegate share, user_share is required
	if selectedDelegateShare != nil && req.UserShare == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":             "user_share is required for accounts with delegate party",
			"delegate_party_id": selectedDelegateShare.PartyID,
		})
		return
	}

	// Use the selected delegate for signing (not necessarily the account's delegate)
	delegateShare = selectedDelegateShare

	// Validate we have enough parties (T+1 required for TSS signing)
	requiredSigners := accountOutput.Account.ThresholdT + 1
	if len(partyIDs) < requiredSigners {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":    "insufficient parties for signing",
			"required": requiredSigners,
			"selected": len(partyIDs),
		})
		return
	}

	// Call session coordinator via gRPC
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Prepare delegate user share if needed
	var delegateUserShare *grpcclient.DelegateUserShareInput
	if delegateShare != nil {
		userShareBytes, err := hex.DecodeString(req.UserShare)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user_share format (expected hex)"})
			return
		}
		delegateUserShare = &grpcclient.DelegateUserShareInput{
			DelegatePartyID: delegateShare.PartyID,
			EncryptedShare:  userShareBytes,
			PartyIndex:      int32(delegateShare.PartyIndex),
		}
		logger.Info("Calling CreateSigningSession with delegate user share",
			zap.String("username", req.Username),
			zap.String("delegate_party_id", delegateShare.PartyID),
			zap.Int("threshold_t", accountOutput.Account.ThresholdT),
			zap.Int("available_parties", len(partyIDs)))
	} else {
		logger.Info("Calling CreateSigningSession via gRPC (auto party selection)",
			zap.String("username", req.Username),
			zap.Int("threshold_t", accountOutput.Account.ThresholdT),
			zap.Int("available_parties", len(partyIDs)),
			zap.String("keygen_session_id", accountOutput.Account.KeygenSessionID.String()))
	}

	// CRITICAL: Pass keygenThresholdN (original n from keygen) for correct TSS math
	resp, err := h.sessionCoordinatorClient.CreateSigningSessionAuto(
		ctx,
		int32(accountOutput.Account.ThresholdT),
		int32(accountOutput.Account.ThresholdN),
		signingParties,
		messageHash,
		600, // 10 minutes expiry
		delegateUserShare,
		accountOutput.Account.KeygenSessionID.String(),
	)

	if err != nil {
		logger.Error("gRPC CreateSigningSession failed", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	logger.Info("gRPC CreateSigningSession succeeded",
		zap.String("session_id", resp.SessionID),
		zap.Int("num_parties", len(resp.SelectedParties)),
		zap.String("keygen_session_id", accountOutput.Account.KeygenSessionID.String()),
		zap.Strings("selected_parties", resp.SelectedParties))

	// Record session_created event for sign
	signSessionID, _ := uuid.Parse(resp.SessionID)
	signEvent := repositories.NewSessionEvent(
		signSessionID,
		req.Username,
		repositories.EventSessionCreated,
		repositories.SessionTypeSign,
	).WithThreshold(accountOutput.Account.ThresholdN, accountOutput.Account.ThresholdT).
		WithMessageHash(messageHash).
		WithMetadata(map[string]interface{}{
			"selected_parties":       resp.SelectedParties,
			"has_delegate":           delegateShare != nil,
			"signing_parties_config": accountOutput.Account.HasSigningPartiesConfig(),
		})
	if delegateShare != nil {
		signEvent = signEvent.WithParty(delegateShare.PartyID, delegateShare.PartyIndex)
	}
	if err := h.sessionEventRepo.Create(c.Request.Context(), signEvent); err != nil {
		logger.Error("Failed to record session event", zap.Error(err))
		// Don't fail the request, just log the error
	}

	response := gin.H{
		"session_id":       resp.SessionID,
		"session_type":     "sign",
		"username":         req.Username,
		"message_hash":     req.MessageHash,
		"threshold_t":      accountOutput.Account.ThresholdT,
		"selected_parties": resp.SelectedParties,
		"status":           "created",
	}

	// Include has_delegate for sign sessions too
	if delegateShare != nil {
		response["has_delegate"] = true
		response["delegate_party_id"] = delegateShare.PartyID
	} else {
		response["has_delegate"] = false
	}

	c.JSON(http.StatusCreated, response)
}

// GetSessionStatus handles getting session status
func (h *AccountHTTPHandler) GetSessionStatus(c *gin.Context) {
	sessionID := c.Param("id")

	// Validate session ID format
	if _, err := uuid.Parse(sessionID); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session ID format"})
		return
	}

	// Call session coordinator via gRPC
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	resp, err := h.sessionCoordinatorClient.GetSessionStatus(ctx, sessionID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	response := gin.H{
		"session_id":        sessionID,
		"status":            resp.Status,
		"session_type":      resp.SessionType, // "keygen" or "sign"
		"completed_parties": resp.CompletedParties,
		"total_parties":     resp.TotalParties,
	}

	// Keygen-specific fields
	if resp.SessionType == "keygen" {
		// has_delegate only meaningful for keygen sessions
		response["has_delegate"] = resp.HasDelegate

		if len(resp.PublicKey) > 0 {
			response["public_key"] = hex.EncodeToString(resp.PublicKey)
		}

		// Include delegate share if keygen session has delegate party
		// has_delegate=true + delegate_share=null means share was already retrieved (one-time)
		// has_delegate=false means no delegate party (pure persistent keygen)
		if resp.HasDelegate && resp.DelegateShare != nil {
			response["delegate_share"] = gin.H{
				"encrypted_share": hex.EncodeToString(resp.DelegateShare.EncryptedShare),
				"party_index":     resp.DelegateShare.PartyIndex,
				"party_id":        resp.DelegateShare.PartyID,
			}
		}
	}

	// Sign-specific fields
	if resp.SessionType == "sign" {
		if len(resp.Signature) > 0 {
			response["signature"] = hex.EncodeToString(resp.Signature)
		}
	}

	c.JSON(http.StatusOK, response)
}

// ============================================
// Account Creation from Keygen (Internal API)
// ============================================

// CreateAccountFromKeygenRequest represents the request from Session Coordinator
// after keygen completion
type CreateAccountFromKeygenRequest struct {
	PublicKey       string                     `json:"public_key" binding:"required"`
	KeygenSessionID string                     `json:"keygen_session_id" binding:"required"`
	ThresholdN      int                        `json:"threshold_n" binding:"required,min=2"`
	ThresholdT      int                        `json:"threshold_t" binding:"required,min=1"`
	Shares          []ShareInfoFromKeygenInput `json:"shares" binding:"required,min=1"`
}

// ShareInfoFromKeygenInput represents share info from keygen
type ShareInfoFromKeygenInput struct {
	PartyID    string `json:"party_id" binding:"required"`
	PartyIndex int    `json:"party_index"`
	ShareType  string `json:"share_type" binding:"required"` // "persistent" or "delegate"
}

// CreateAccountFromKeygen handles account creation after keygen completion
// This is called by Session Coordinator when all parties complete keygen
func (h *AccountHTTPHandler) CreateAccountFromKeygen(c *gin.Context) {
	var req CreateAccountFromKeygenRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Validate threshold
	if req.ThresholdT > req.ThresholdN {
		c.JSON(http.StatusBadRequest, gin.H{"error": "threshold_t cannot be greater than threshold_n"})
		return
	}

	// Decode public key
	publicKey, err := hex.DecodeString(req.PublicKey)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid public key format"})
		return
	}

	// Parse keygen session ID
	keygenSessionID, err := uuid.Parse(req.KeygenSessionID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid keygen_session_id format"})
		return
	}

	// Generate a unique username based on keygen session ID
	// In production, you might want a different naming scheme
	username := "wallet-" + req.KeygenSessionID[:8]

	// Convert shares - map share type string to value_objects.ShareType
	shares := make([]ports.ShareInput, len(req.Shares))
	for i, s := range req.Shares {
		var shareType value_objects.ShareType
		switch s.ShareType {
		case "persistent", "server":
			shareType = value_objects.ShareTypeServer
		case "delegate", "user_device":
			shareType = value_objects.ShareTypeUserDevice
		default:
			shareType = value_objects.ShareTypeServer
		}
		shares[i] = ports.ShareInput{
			ShareType:  shareType,
			PartyID:    s.PartyID,
			PartyIndex: s.PartyIndex,
		}
	}

	logger.Info("Creating account from keygen",
		zap.String("keygen_session_id", req.KeygenSessionID),
		zap.String("username", username),
		zap.Int("threshold_n", req.ThresholdN),
		zap.Int("threshold_t", req.ThresholdT),
		zap.Int("num_shares", len(shares)))

	// Create account
	output, err := h.createAccountUC.Execute(c.Request.Context(), ports.CreateAccountInput{
		Username:        username,
		PublicKey:       publicKey,
		KeygenSessionID: keygenSessionID,
		ThresholdN:      req.ThresholdN,
		ThresholdT:      req.ThresholdT,
		Shares:          shares,
	})
	if err != nil {
		logger.Error("Failed to create account from keygen",
			zap.String("keygen_session_id", req.KeygenSessionID),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	logger.Info("Account created from keygen successfully",
		zap.String("account_id", output.Account.ID.String()),
		zap.String("username", output.Account.Username),
		zap.String("keygen_session_id", req.KeygenSessionID))

	c.JSON(http.StatusCreated, gin.H{
		"account_id": output.Account.ID.String(),
		"username":   output.Account.Username,
		"public_key": hex.EncodeToString(output.Account.PublicKey),
	})
}

// ============================================
// Signing Parties Configuration Endpoints
// ============================================

// SigningPartiesRequest represents the request for setting/updating signing parties
type SigningPartiesRequest struct {
	PartyIDs []string `json:"party_ids" binding:"required,min=1"`
}

// SetSigningParties handles setting signing parties for the first time
// POST /accounts/by-username/:username/signing-config
func (h *AccountHTTPHandler) SetSigningParties(c *gin.Context) {
	username := c.Param("username")
	if username == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "username is required"})
		return
	}

	var req SigningPartiesRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Get account by username
	accountOutput, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{
		Username: &username,
	})
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "account not found for username: " + username})
		return
	}

	// Check if signing parties already configured
	if accountOutput.Account.HasSigningPartiesConfig() {
		c.JSON(http.StatusConflict, gin.H{
			"error":                   "signing parties already configured, use PUT to update",
			"current_signing_parties": accountOutput.Account.GetSigningParties(),
		})
		return
	}

	// Validate that all party IDs are valid shares for this account
	validPartyIDs := make(map[string]bool)
	for _, share := range accountOutput.Shares {
		if share.IsActive {
			validPartyIDs[share.PartyID] = true
		}
	}

	for _, partyID := range req.PartyIDs {
		if !validPartyIDs[partyID] {
			c.JSON(http.StatusBadRequest, gin.H{
				"error":    "invalid party ID - not an active share for this account",
				"party_id": partyID,
			})
			return
		}
	}

	// Set signing parties
	if err := accountOutput.Account.SetSigningParties(req.PartyIDs); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Update account in database
	_, err = h.updateAccountUC.Execute(c.Request.Context(), ports.UpdateAccountInput{
		AccountID:      accountOutput.Account.ID,
		SigningParties: req.PartyIDs,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	logger.Info("Signing parties configured",
		zap.String("username", username),
		zap.Strings("signing_parties", req.PartyIDs))

	// Record signing_config_set event
	event := repositories.NewSessionEvent(
		uuid.New(), // Generate new event ID (no session for config changes)
		username,
		repositories.EventSigningConfigSet,
		repositories.SessionTypeSign, // Configuration is for signing
	).WithThreshold(accountOutput.Account.ThresholdN, accountOutput.Account.ThresholdT).
		WithMetadata(map[string]interface{}{
			"operation":       "set",
			"signing_parties": req.PartyIDs,
		})
	if err := h.sessionEventRepo.Create(c.Request.Context(), event); err != nil {
		logger.Error("Failed to record signing config event", zap.Error(err))
	}

	c.JSON(http.StatusCreated, gin.H{
		"message":         "signing parties configured successfully",
		"username":        username,
		"signing_parties": req.PartyIDs,
		"threshold_t":     accountOutput.Account.ThresholdT,
	})
}

// UpdateSigningParties handles updating existing signing parties configuration
// PUT /accounts/by-username/:username/signing-config
func (h *AccountHTTPHandler) UpdateSigningParties(c *gin.Context) {
	username := c.Param("username")
	if username == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "username is required"})
		return
	}

	var req SigningPartiesRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Get account by username
	accountOutput, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{
		Username: &username,
	})
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "account not found for username: " + username})
		return
	}

	// Check if signing parties are configured (must exist to update)
	if !accountOutput.Account.HasSigningPartiesConfig() {
		c.JSON(http.StatusNotFound, gin.H{
			"error": "signing parties not configured, use POST to set",
		})
		return
	}

	// Validate that all party IDs are valid shares for this account
	validPartyIDs := make(map[string]bool)
	for _, share := range accountOutput.Shares {
		if share.IsActive {
			validPartyIDs[share.PartyID] = true
		}
	}

	for _, partyID := range req.PartyIDs {
		if !validPartyIDs[partyID] {
			c.JSON(http.StatusBadRequest, gin.H{
				"error":    "invalid party ID - not an active share for this account",
				"party_id": partyID,
			})
			return
		}
	}

	// Set signing parties (validates count matches threshold)
	if err := accountOutput.Account.SetSigningParties(req.PartyIDs); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Update account in database
	_, err = h.updateAccountUC.Execute(c.Request.Context(), ports.UpdateAccountInput{
		AccountID:      accountOutput.Account.ID,
		SigningParties: req.PartyIDs,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	logger.Info("Signing parties updated",
		zap.String("username", username),
		zap.Strings("signing_parties", req.PartyIDs))

	// Record signing_config_set event for update
	updateEvent := repositories.NewSessionEvent(
		uuid.New(),
		username,
		repositories.EventSigningConfigSet,
		repositories.SessionTypeSign,
	).WithThreshold(accountOutput.Account.ThresholdN, accountOutput.Account.ThresholdT).
		WithMetadata(map[string]interface{}{
			"operation":       "update",
			"signing_parties": req.PartyIDs,
		})
	if err := h.sessionEventRepo.Create(c.Request.Context(), updateEvent); err != nil {
		logger.Error("Failed to record signing config event", zap.Error(err))
	}

	c.JSON(http.StatusOK, gin.H{
		"message":         "signing parties updated successfully",
		"username":        username,
		"signing_parties": req.PartyIDs,
		"threshold_t":     accountOutput.Account.ThresholdT,
	})
}

// ClearSigningParties handles clearing signing parties configuration
// DELETE /accounts/by-username/:username/signing-config
func (h *AccountHTTPHandler) ClearSigningParties(c *gin.Context) {
	username := c.Param("username")
	if username == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "username is required"})
		return
	}

	// Get account by username
	accountOutput, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{
		Username: &username,
	})
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "account not found for username: " + username})
		return
	}

	// Check if signing parties are configured
	if !accountOutput.Account.HasSigningPartiesConfig() {
		c.JSON(http.StatusNotFound, gin.H{
			"error": "signing parties not configured",
		})
		return
	}

	// Clear signing parties - pass empty slice
	_, err = h.updateAccountUC.Execute(c.Request.Context(), ports.UpdateAccountInput{
		AccountID:           accountOutput.Account.ID,
		ClearSigningParties: true,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	logger.Info("Signing parties cleared",
		zap.String("username", username))

	// Record signing_config_cleared event
	clearEvent := repositories.NewSessionEvent(
		uuid.New(),
		username,
		repositories.EventSigningConfigCleared,
		repositories.SessionTypeSign,
	).WithThreshold(accountOutput.Account.ThresholdN, accountOutput.Account.ThresholdT).
		WithMetadata(map[string]interface{}{
			"operation": "clear",
		})
	if err := h.sessionEventRepo.Create(c.Request.Context(), clearEvent); err != nil {
		logger.Error("Failed to record signing config event", zap.Error(err))
	}

	c.JSON(http.StatusOK, gin.H{
		"message":  "signing parties cleared - all active parties will be used for signing",
		"username": username,
	})
}

// GetSigningParties handles getting current signing parties configuration
// GET /accounts/by-username/:username/signing-config
func (h *AccountHTTPHandler) GetSigningParties(c *gin.Context) {
	username := c.Param("username")
	if username == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "username is required"})
		return
	}

	// Get account by username
	accountOutput, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{
		Username: &username,
	})
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "account not found for username: " + username})
		return
	}

	if accountOutput.Account.HasSigningPartiesConfig() {
		c.JSON(http.StatusOK, gin.H{
			"configured":      true,
			"username":        username,
			"signing_parties": accountOutput.Account.GetSigningParties(),
			"threshold_t":     accountOutput.Account.ThresholdT,
		})
	} else {
		// Get all active parties
		var activeParties []string
		for _, share := range accountOutput.Shares {
			if share.IsActive {
				activeParties = append(activeParties, share.PartyID)
			}
		}
		c.JSON(http.StatusOK, gin.H{
			"configured":     false,
			"username":       username,
			"message":        "no signing parties configured - all active parties will be used",
			"active_parties": activeParties,
			"threshold_t":    accountOutput.Account.ThresholdT,
		})
	}
}
