package http

import (
	"context"
	"crypto/rand"
	"database/sql"
	"encoding/hex"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	grpcclient "github.com/rwadurian/mpc-system/services/account/adapters/output/grpc"
	"github.com/rwadurian/mpc-system/pkg/jwt"
	"github.com/rwadurian/mpc-system/pkg/logger"
	"go.uber.org/zap"
)

// CoManagedHTTPHandler handles HTTP requests for co-managed wallets
// This is a completely independent handler that does not affect existing functionality
type CoManagedHTTPHandler struct {
	sessionCoordinatorClient *grpcclient.SessionCoordinatorClient
	db                       *sql.DB         // Database connection for invite_code lookups
	jwtService               *jwt.JWTService // JWT service for generating join tokens
}

// NewCoManagedHTTPHandler creates a new CoManagedHTTPHandler
func NewCoManagedHTTPHandler(
	sessionCoordinatorClient *grpcclient.SessionCoordinatorClient,
) *CoManagedHTTPHandler {
	return &CoManagedHTTPHandler{
		sessionCoordinatorClient: sessionCoordinatorClient,
		db:                       nil,
	}
}

// NewCoManagedHTTPHandlerWithDB creates a new CoManagedHTTPHandler with database support
func NewCoManagedHTTPHandlerWithDB(
	sessionCoordinatorClient *grpcclient.SessionCoordinatorClient,
	db *sql.DB,
	jwtService *jwt.JWTService,
) *CoManagedHTTPHandler {
	return &CoManagedHTTPHandler{
		sessionCoordinatorClient: sessionCoordinatorClient,
		db:                       db,
		jwtService:               jwtService,
	}
}

// RegisterRoutes registers HTTP routes for co-managed wallets
func (h *CoManagedHTTPHandler) RegisterRoutes(router *gin.RouterGroup) {
	coManaged := router.Group("/co-managed")
	{
		// Keygen session routes
		coManaged.POST("/sessions", h.CreateSession)
		coManaged.POST("/sessions/:sessionId/join", h.JoinSession)
		coManaged.GET("/sessions/:sessionId", h.GetSessionStatus)
		coManaged.GET("/sessions/by-invite-code/:inviteCode", h.GetSessionByInviteCode)

		// Sign session routes (new - does not affect existing functionality)
		coManaged.POST("/sign", h.CreateSignSession)
		coManaged.GET("/sign/:sessionId", h.GetSignSessionStatus)
		coManaged.GET("/sign/by-invite-code/:inviteCode", h.GetSignSessionByInviteCode)
	}
}

// generateInviteCode generates a random invite code in format XXXX-XXXX-XXXX
func generateInviteCode() string {
	bytes := make([]byte, 6)
	rand.Read(bytes)
	code := fmt.Sprintf("%X", bytes)
	return fmt.Sprintf("%s-%s-%s", code[0:4], code[4:8], code[8:12])
}

// ============================================
// Create Co-Managed Wallet Session
// ============================================

// CreateCoManagedSessionRequest represents the request for creating a co-managed wallet session
type CreateCoManagedSessionRequest struct {
	WalletName       string `json:"wallet_name" binding:"required"`       // Wallet name
	ThresholdT       int    `json:"threshold_t" binding:"required,min=1"` // Signing threshold (actual signers needed = t+1)
	ThresholdN       int    `json:"threshold_n" binding:"required,min=2"` // Total parties
	InitiatorPartyID string `json:"initiator_party_id" binding:"required"` // Initiator's party ID
	InitiatorName    string `json:"initiator_name"`                        // Initiator's display name
	PersistentCount  int    `json:"persistent_count"`                      // Number of server parties (default 2)
}

// CreateSession handles creating a new co-managed wallet session
func (h *CoManagedHTTPHandler) CreateSession(c *gin.Context) {
	var req CreateCoManagedSessionRequest
	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 must be less than threshold_n"})
		return
	}

	// Default persistent count is 2 (platform backup parties)
	persistentCount := req.PersistentCount
	if persistentCount <= 0 {
		persistentCount = 2
	}

	// Calculate external party count
	externalCount := req.ThresholdN - persistentCount
	if externalCount < 1 {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "threshold_n must be greater than persistent_count to allow external participants",
		})
		return
	}

	// Generate invite code
	inviteCode := generateInviteCode()

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

	logger.Info("Creating co-managed keygen session",
		zap.String("wallet_name", req.WalletName),
		zap.Int("threshold_n", req.ThresholdN),
		zap.Int("threshold_t", req.ThresholdT),
		zap.Int("persistent_count", persistentCount),
		zap.Int("external_count", externalCount),
		zap.String("initiator_party_id", req.InitiatorPartyID))

	resp, err := h.sessionCoordinatorClient.CreateCoManagedKeygenSession(
		ctx,
		req.WalletName,
		inviteCode,
		int32(req.ThresholdN),
		int32(req.ThresholdT),
		int32(persistentCount),
		int32(externalCount),
		3600, // 1 hour expiry for session creation phase
	)

	if err != nil {
		logger.Error("Failed to create co-managed keygen session", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Get wildcard join token for external participants
	wildcardToken := ""
	if token, ok := resp.JoinTokens["*"]; ok {
		wildcardToken = token
	}

	logger.Info("Co-managed keygen session created successfully",
		zap.String("session_id", resp.SessionID),
		zap.String("invite_code", inviteCode),
		zap.Int("num_server_parties", len(resp.SelectedServerParties)))

	c.JSON(http.StatusCreated, gin.H{
		"session_id":              resp.SessionID,
		"wallet_name":             req.WalletName,
		"invite_code":             inviteCode,
		"join_token":              wildcardToken,     // Token for external participants (backward compatible)
		"join_tokens":             resp.JoinTokens,   // Full join tokens map for service-party-app
		"threshold_n":             req.ThresholdN,
		"threshold_t":             req.ThresholdT,
		"selected_server_parties": resp.SelectedServerParties,
		"status":                  "waiting_for_participants",
		"current_participants":    len(resp.SelectedServerParties), // Server parties auto-joined
		"required_participants":   req.ThresholdN,
		"expires_at":              resp.ExpiresAt,
	})
}

// ============================================
// Join Co-Managed Wallet Session
// ============================================

// JoinSessionRequest represents the request for joining a session
type JoinSessionRequest struct {
	PartyID         string `json:"party_id" binding:"required"`   // Participant's party ID
	JoinToken       string `json:"join_token" binding:"required"` // Join token (from invite)
	ParticipantName string `json:"participant_name"`              // Display name
	DeviceType      string `json:"device_type"`                   // Device type (pc, android, ios)
	DeviceID        string `json:"device_id"`                     // Device ID
}

// JoinSession handles joining an existing co-managed session
func (h *CoManagedHTTPHandler) JoinSession(c *gin.Context) {
	sessionID := c.Param("sessionId")
	if sessionID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "session_id is required"})
		return
	}

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

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

	// Default device type
	deviceType := req.DeviceType
	if deviceType == "" {
		deviceType = "pc"
	}

	deviceID := req.DeviceID
	if deviceID == "" {
		deviceID = req.PartyID // Use party ID as device ID if not provided
	}

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

	logger.Info("Joining co-managed session",
		zap.String("session_id", sessionID),
		zap.String("party_id", req.PartyID),
		zap.String("participant_name", req.ParticipantName))

	resp, err := h.sessionCoordinatorClient.JoinSession(
		ctx,
		sessionID,
		req.PartyID,
		req.JoinToken,
		deviceType,
		deviceID,
	)

	if err != nil {
		logger.Error("Failed to join session", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if !resp.Success {
		c.JSON(http.StatusBadRequest, gin.H{"error": "failed to join session"})
		return
	}

	// Build other parties list
	otherParties := make([]gin.H, 0, len(resp.OtherParties))
	for _, p := range resp.OtherParties {
		otherParties = append(otherParties, gin.H{
			"party_id":    p.PartyID,
			"party_index": p.PartyIndex,
		})
	}

	result := gin.H{
		"success":       true,
		"party_index":   resp.PartyIndex,
		"other_parties": otherParties,
	}

	if resp.SessionInfo != nil {
		result["session_info"] = gin.H{
			"session_id":   resp.SessionInfo.SessionID,
			"session_type": resp.SessionInfo.SessionType,
			"threshold_n":  resp.SessionInfo.ThresholdN,
			"threshold_t":  resp.SessionInfo.ThresholdT,
			"status":       resp.SessionInfo.Status,
			"wallet_name":  resp.SessionInfo.WalletName,
		}
	}

	logger.Info("Joined co-managed session successfully",
		zap.String("session_id", sessionID),
		zap.String("party_id", req.PartyID),
		zap.Int32("party_index", resp.PartyIndex))

	c.JSON(http.StatusOK, result)
}

// ============================================
// Get Session Status
// ============================================

// GetSessionStatus handles getting the status of a co-managed session
func (h *CoManagedHTTPHandler) GetSessionStatus(c *gin.Context) {
	sessionID := c.Param("sessionId")
	if sessionID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "session_id is required"})
		return
	}

	// 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 {
		logger.Error("Failed to get session status", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	result := gin.H{
		"session_id":        sessionID,
		"status":            resp.Status,
		"session_type":      resp.SessionType,
		"threshold_t":       resp.ThresholdT,
		"threshold_n":       resp.ThresholdN,
		"completed_parties": resp.CompletedParties,
		"total_parties":     resp.TotalParties,
	}

	// Add public key if keygen completed
	if resp.SessionType == "co_managed_keygen" && len(resp.PublicKey) > 0 {
		result["public_key"] = hex.EncodeToString(resp.PublicKey)
	}

	// Include participants with party_index (for service-party-app to build correct participant list)
	if len(resp.Participants) > 0 {
		participants := make([]gin.H, len(resp.Participants))
		for i, p := range resp.Participants {
			participants[i] = gin.H{
				"party_id":    p.PartyID,
				"party_index": p.PartyIndex,
				"status":      p.Status,
			}
		}
		result["participants"] = participants
	}

	c.JSON(http.StatusOK, result)
}

// ============================================
// Get Session By Invite Code
// ============================================

// GetSessionByInviteCode handles looking up a session by its invite code
// This allows Service-Party-App to find the session_id from an invite_code
func (h *CoManagedHTTPHandler) GetSessionByInviteCode(c *gin.Context) {
	inviteCode := c.Param("inviteCode")
	if inviteCode == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invite_code is required"})
		return
	}

	// Check if database connection is available
	if h.db == nil {
		logger.Error("Database connection not available for invite_code lookup")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "service configuration error"})
		return
	}

	// Query database for session by invite_code
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var sessionID string
	var walletName string
	var thresholdN, thresholdT int
	var status string
	var expiresAt time.Time

	err := h.db.QueryRowContext(ctx, `
		SELECT id, COALESCE(wallet_name, ''), threshold_n, threshold_t, status, expires_at
		FROM mpc_sessions
		WHERE invite_code = $1 AND session_type = 'co_managed_keygen'
	`, inviteCode).Scan(&sessionID, &walletName, &thresholdN, &thresholdT, &status, &expiresAt)

	if err != nil {
		if err == sql.ErrNoRows {
			logger.Info("Session not found for invite_code",
				zap.String("invite_code", inviteCode))
			c.JSON(http.StatusNotFound, gin.H{"error": "session not found or expired"})
			return
		}
		logger.Error("Failed to query session by invite_code",
			zap.String("invite_code", inviteCode),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to lookup session"})
		return
	}

	// Check if session is expired
	if time.Now().After(expiresAt) {
		logger.Info("Session expired for invite_code",
			zap.String("invite_code", inviteCode),
			zap.String("session_id", sessionID))
		c.JSON(http.StatusGone, gin.H{"error": "session has expired"})
		return
	}

	// Get session status from session coordinator
	statusResp, err := h.sessionCoordinatorClient.GetSessionStatus(ctx, sessionID)
	if err != nil {
		logger.Error("Failed to get session status from coordinator",
			zap.String("session_id", sessionID),
			zap.Error(err))
		// Return basic info without join token
		c.JSON(http.StatusOK, gin.H{
			"session_id":   sessionID,
			"wallet_name":  walletName,
			"threshold_n":  thresholdN,
			"threshold_t":  thresholdT,
			"status":       status,
			"expires_at":   expiresAt.UnixMilli(),
		})
		return
	}

	// Generate a wildcard join token for this session
	// This allows any participant to join using this token
	var joinToken string
	if h.jwtService != nil {
		sessionUUID, err := uuid.Parse(sessionID)
		if err == nil {
			// Token valid until session expires
			tokenExpiry := time.Until(expiresAt)
			if tokenExpiry > 0 {
				joinToken, err = h.jwtService.GenerateJoinToken(sessionUUID, "*", tokenExpiry)
				if err != nil {
					logger.Warn("Failed to generate join token",
						zap.String("session_id", sessionID),
						zap.Error(err))
				}
			}
		}
	}

	logger.Info("Found session for invite_code",
		zap.String("invite_code", inviteCode),
		zap.String("session_id", sessionID),
		zap.String("wallet_name", walletName),
		zap.Bool("has_join_token", joinToken != ""))

	c.JSON(http.StatusOK, gin.H{
		"session_id":        sessionID,
		"wallet_name":       walletName,
		"threshold_n":       thresholdN,
		"threshold_t":       thresholdT,
		"status":            statusResp.Status,
		"completed_parties": statusResp.CompletedParties,
		"total_parties":     statusResp.TotalParties,
		"expires_at":        expiresAt.UnixMilli(),
		"join_token":        joinToken,
	})
}

// ============================================
// Co-Managed Sign Session (NEW - Independent)
// ============================================

// SignPartyInfo contains party information for signing
type SignPartyInfo struct {
	PartyID    string `json:"party_id" binding:"required"`
	PartyIndex int32  `json:"party_index" binding:"required"`
}

// CreateSignSessionRequest represents the request for creating a co-managed sign session
type CreateSignSessionRequest struct {
	KeygenSessionID string          `json:"keygen_session_id" binding:"required"` // The keygen session that created the wallet
	WalletName      string          `json:"wallet_name"`                          // Wallet name (for display)
	MessageHash     string          `json:"message_hash" binding:"required"`      // Hex-encoded message hash to sign
	Parties         []SignPartyInfo `json:"parties" binding:"required,min=1"`     // Parties to participate in signing (t+1)
	ThresholdT      int             `json:"threshold_t" binding:"required,min=1"` // Signing threshold
	InitiatorName   string          `json:"initiator_name"`                       // Initiator's display name
}

// CreateSignSession handles creating a new co-managed sign session
// This is a completely new endpoint that does not affect existing sign functionality
func (h *CoManagedHTTPHandler) CreateSignSession(c *gin.Context) {
	var req CreateSignSessionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

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

	// Validate message_hash (should be hex encoded)
	messageHash, err := hex.DecodeString(req.MessageHash)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "message_hash must be hex encoded"})
		return
	}

	// Validate party count == threshold_t (for t-of-n signing, exactly t parties are needed)
	if len(req.Parties) != req.ThresholdT {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": fmt.Sprintf("need exactly %d parties for threshold %d, got %d", req.ThresholdT, req.ThresholdT, len(req.Parties)),
		})
		return
	}

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

	// CRITICAL: Query keygen session to get the original threshold_n
	// This is required for TSS signing to work correctly - the n value must match keygen
	var keygenThresholdN, keygenThresholdT int
	if h.db != nil {
		err = h.db.QueryRowContext(ctx, `
			SELECT threshold_n, threshold_t
			FROM mpc_sessions
			WHERE id = $1
		`, req.KeygenSessionID).Scan(&keygenThresholdN, &keygenThresholdT)
		if err != nil {
			logger.Error("Failed to query keygen session for threshold values",
				zap.String("keygen_session_id", req.KeygenSessionID),
				zap.Error(err))
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to lookup keygen session"})
			return
		}
	} else {
		logger.Error("Database connection not available for keygen session lookup")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "service configuration error"})
		return
	}

	// Generate invite code for sign session
	inviteCode := generateInviteCode()

	// Convert parties to gRPC format
	parties := make([]grpcclient.SigningPartyInfo, len(req.Parties))
	for i, p := range req.Parties {
		parties[i] = grpcclient.SigningPartyInfo{
			PartyID:    p.PartyID,
			PartyIndex: p.PartyIndex,
		}
	}

	logger.Info("Creating co-managed sign session",
		zap.String("keygen_session_id", req.KeygenSessionID),
		zap.String("wallet_name", req.WalletName),
		zap.Int("keygen_threshold_n", keygenThresholdN),
		zap.Int("keygen_threshold_t", keygenThresholdT),
		zap.Int("signing_threshold_t", req.ThresholdT),
		zap.Int("num_signing_parties", len(req.Parties)),
		zap.String("invite_code", inviteCode))

	// Create signing session
	// Note: delegateUserShare is nil for co-managed wallets (no delegate party)
	// CRITICAL: Pass keygenThresholdN (original n from keygen) for correct TSS math
	resp, err := h.sessionCoordinatorClient.CreateSigningSessionAuto(
		ctx,
		int32(req.ThresholdT),
		int32(keygenThresholdN),
		parties,
		messageHash,
		86400, // 24 hour expiry
		nil,   // No delegate share for co-managed wallets
		req.KeygenSessionID,
	)

	if err != nil {
		logger.Error("Failed to create co-managed sign session", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Store invite_code mapping in database (for lookup)
	if h.db != nil {
		_, dbErr := h.db.ExecContext(ctx, `
			UPDATE mpc_sessions
			SET invite_code = $1, wallet_name = $2
			WHERE id = $3
		`, inviteCode, req.WalletName, resp.SessionID)
		if dbErr != nil {
			logger.Warn("Failed to store invite_code for sign session",
				zap.String("session_id", resp.SessionID),
				zap.Error(dbErr))
			// Don't fail the request, just log the warning
		}
	}

	// Get wildcard token for backward compatibility (join_token field)
	wildcardToken := ""
	if token, ok := resp.JoinTokens["*"]; ok {
		wildcardToken = token
	}

	logger.Info("Co-managed sign session created successfully",
		zap.String("session_id", resp.SessionID),
		zap.String("invite_code", inviteCode),
		zap.Int("num_parties", len(resp.SelectedParties)),
		zap.Int("num_join_tokens", len(resp.JoinTokens)))

	c.JSON(http.StatusCreated, gin.H{
		"session_id":            resp.SessionID,
		"keygen_session_id":     req.KeygenSessionID,
		"wallet_name":           req.WalletName,
		"invite_code":           inviteCode,
		"join_token":            wildcardToken,    // Backward compatible: wildcard token (may be empty)
		"join_tokens":           resp.JoinTokens,  // New: all join tokens (map[partyID]token)
		"threshold_n":           keygenThresholdN, // Original N from keygen (required for TSS)
		"threshold_t":           req.ThresholdT,
		"selected_parties":      resp.SelectedParties,
		"status":                "waiting_for_participants",
		"current_participants":  0,
		"required_participants": len(req.Parties),
		"expires_at":            resp.ExpiresAt,
	})
}

// GetSignSessionStatus handles getting the status of a co-managed sign session
func (h *CoManagedHTTPHandler) GetSignSessionStatus(c *gin.Context) {
	sessionID := c.Param("sessionId")
	if sessionID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "session_id is required"})
		return
	}

	// 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 {
		logger.Error("Failed to get sign session status", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Get invite_code from database
	var inviteCode string
	if h.db != nil {
		row := h.db.QueryRowContext(ctx, `SELECT invite_code FROM mpc_sessions WHERE id = $1`, sessionID)
		row.Scan(&inviteCode) // Ignore error, invite_code is optional
	}

	result := gin.H{
		"session_id":        sessionID,
		"status":            resp.Status,
		"session_type":      resp.SessionType,
		"threshold_t":       resp.ThresholdT,
		"threshold_n":       resp.ThresholdN,
		"completed_parties": resp.CompletedParties,
		"total_parties":     resp.TotalParties,
	}

	// Add invite_code if available
	if inviteCode != "" {
		result["invite_code"] = inviteCode
	}

	// Add signature if sign completed
	if resp.SessionType == "sign" && len(resp.Signature) > 0 {
		result["signature"] = hex.EncodeToString(resp.Signature)
	}

	// Include participants with party_index
	if len(resp.Participants) > 0 {
		participants := make([]gin.H, len(resp.Participants))
		for i, p := range resp.Participants {
			participants[i] = gin.H{
				"party_id":    p.PartyID,
				"party_index": p.PartyIndex,
				"status":      p.Status,
			}
		}
		result["participants"] = participants
	}

	c.JSON(http.StatusOK, result)
}

// GetSignSessionByInviteCode handles looking up a sign session by its invite code
// This is a completely new endpoint that does not affect existing functionality
func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
	inviteCode := c.Param("inviteCode")
	if inviteCode == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invite_code is required"})
		return
	}

	// Check if database connection is available
	if h.db == nil {
		logger.Error("Database connection not available for sign invite_code lookup")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "service configuration error"})
		return
	}

	// Query database for sign session by invite_code
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var sessionID string
	var walletName string
	var keygenSessionID string
	var status string
	var expiresAt time.Time
	var messageHash []byte

	// Query sign session basic info
	err := h.db.QueryRowContext(ctx, `
		SELECT id, COALESCE(wallet_name, ''), COALESCE(keygen_session_id::text, ''),
		       status, expires_at, COALESCE(message_hash, '')
		FROM mpc_sessions
		WHERE invite_code = $1 AND session_type = 'sign' AND status != 'failed'
		ORDER BY created_at DESC
		LIMIT 1
	`, inviteCode).Scan(&sessionID, &walletName, &keygenSessionID, &status, &expiresAt, &messageHash)

	if err != nil {
		if err == sql.ErrNoRows {
			logger.Info("Sign session not found for invite_code",
				zap.String("invite_code", inviteCode))
			c.JSON(http.StatusNotFound, gin.H{"error": "sign session not found or expired"})
			return
		}
		logger.Error("Failed to query sign session by invite_code",
			zap.String("invite_code", inviteCode),
			zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to lookup sign session"})
		return
	}

	// Check if session is expired
	if time.Now().After(expiresAt) {
		logger.Info("Sign session expired for invite_code",
			zap.String("invite_code", inviteCode),
			zap.String("session_id", sessionID))
		c.JSON(http.StatusGone, gin.H{"error": "sign session has expired"})
		return
	}

	// Get threshold_n and threshold_t from the KEYGEN session (the authoritative source)
	// This is critical for TSS signing to work correctly
	var keygenThresholdN, keygenThresholdT int
	if keygenSessionID != "" {
		err = h.db.QueryRowContext(ctx, `
			SELECT threshold_n, threshold_t
			FROM mpc_sessions
			WHERE id = $1
		`, keygenSessionID).Scan(&keygenThresholdN, &keygenThresholdT)
		if err != nil {
			logger.Error("Failed to query keygen session for threshold values",
				zap.String("keygen_session_id", keygenSessionID),
				zap.Error(err))
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to lookup keygen session"})
			return
		}
	} else {
		logger.Error("Sign session has no keygen_session_id",
			zap.String("session_id", sessionID))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "sign session missing keygen reference"})
		return
	}

	// Get the signing parties list from the sign session's participants table
	// These are the parties that were selected for this signing session
	var parties []gin.H
	rows, err := h.db.QueryContext(ctx, `
		SELECT party_id, party_index
		FROM participants
		WHERE session_id = $1
		ORDER BY party_index
	`, sessionID)
	if err != nil {
		logger.Error("Failed to query sign session participants",
			zap.String("session_id", sessionID),
			zap.Error(err))
		// Continue without parties list, frontend will fallback
	} else {
		defer rows.Close()
		for rows.Next() {
			var partyID string
			var partyIndex int
			if err := rows.Scan(&partyID, &partyIndex); err != nil {
				logger.Warn("Failed to scan participant row",
					zap.Error(err))
				continue
			}
			parties = append(parties, gin.H{
				"party_id":    partyID,
				"party_index": partyIndex,
			})
		}
	}

	// Get session status from coordinator
	statusResp, err := h.sessionCoordinatorClient.GetSessionStatus(ctx, sessionID)
	if err != nil {
		logger.Error("Failed to get sign session status from coordinator",
			zap.String("session_id", sessionID),
			zap.Error(err))
		// Return basic info without detailed status
		c.JSON(http.StatusOK, gin.H{
			"session_id":        sessionID,
			"keygen_session_id": keygenSessionID,
			"wallet_name":       walletName,
			"message_hash":      hex.EncodeToString(messageHash),
			"threshold_n":       keygenThresholdN,
			"threshold_t":       keygenThresholdT,
			"status":            status,
			"joined_count":      0,
			"expires_at":        expiresAt.UnixMilli(),
			"parties":           parties,
		})
		return
	}

	// Generate join token for this session (wildcard token that allows any party to join)
	var joinToken string
	if h.jwtService != nil {
		sessionUUID, parseErr := uuid.Parse(sessionID)
		if parseErr == nil {
			token, err := h.jwtService.GenerateJoinToken(sessionUUID, "*", time.Hour) // Wildcard party ID, 1 hour expiry
			if err != nil {
				logger.Warn("Failed to generate join token for sign session",
					zap.String("session_id", sessionID),
					zap.Error(err))
			} else {
				joinToken = token
			}
		}
	}

	logger.Info("Found sign session for invite_code",
		zap.String("invite_code", inviteCode),
		zap.String("session_id", sessionID),
		zap.String("wallet_name", walletName),
		zap.String("keygen_session_id", keygenSessionID),
		zap.Int("keygen_threshold_n", keygenThresholdN),
		zap.Int("keygen_threshold_t", keygenThresholdT),
		zap.Int("parties_count", len(parties)),
		zap.Bool("has_join_token", joinToken != ""))

	c.JSON(http.StatusOK, gin.H{
		"session_id":        sessionID,
		"keygen_session_id": keygenSessionID,
		"wallet_name":       walletName,
		"message_hash":      hex.EncodeToString(messageHash),
		"threshold_n":       keygenThresholdN,
		"threshold_t":       keygenThresholdT,
		"status":            statusResp.Status,
		"joined_count":      statusResp.CompletedParties,
		"expires_at":        expiresAt.UnixMilli(),
		"join_token":        joinToken,
		"parties":           parties,
	})
}
