package http

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/rwadurian/mpc-system/pkg/logger"
	"github.com/rwadurian/mpc-system/services/session-coordinator/application/ports/input"
	"github.com/rwadurian/mpc-system/services/session-coordinator/application/use_cases"
	"github.com/rwadurian/mpc-system/services/session-coordinator/domain/entities"
	"github.com/rwadurian/mpc-system/services/session-coordinator/domain/repositories"
	"github.com/rwadurian/mpc-system/services/session-coordinator/domain/value_objects"
	"go.uber.org/zap"
)

// SessionHTTPHandler handles HTTP requests for session management
type SessionHTTPHandler struct {
	createSessionUC     *use_cases.CreateSessionUseCase
	joinSessionUC       *use_cases.JoinSessionUseCase
	getSessionStatusUC  *use_cases.GetSessionStatusUseCase
	reportCompletionUC  *use_cases.ReportCompletionUseCase
	closeSessionUC      *use_cases.CloseSessionUseCase
	sessionRepo         repositories.SessionRepository
}

// NewSessionHTTPHandler creates a new HTTP handler
func NewSessionHTTPHandler(
	createSessionUC *use_cases.CreateSessionUseCase,
	joinSessionUC *use_cases.JoinSessionUseCase,
	getSessionStatusUC *use_cases.GetSessionStatusUseCase,
	reportCompletionUC *use_cases.ReportCompletionUseCase,
	closeSessionUC *use_cases.CloseSessionUseCase,
	sessionRepo repositories.SessionRepository,
) *SessionHTTPHandler {
	return &SessionHTTPHandler{
		createSessionUC:    createSessionUC,
		joinSessionUC:      joinSessionUC,
		getSessionStatusUC: getSessionStatusUC,
		reportCompletionUC: reportCompletionUC,
		closeSessionUC:     closeSessionUC,
		sessionRepo:        sessionRepo,
	}
}

// RegisterRoutes registers HTTP routes
func (h *SessionHTTPHandler) RegisterRoutes(r *gin.RouterGroup) {
	sessions := r.Group("/sessions")
	{
		sessions.POST("", h.CreateSession)
		sessions.POST("/join", h.JoinSessionByToken)
		sessions.POST("/:id/join", h.JoinSession)
		sessions.GET("/:id", h.GetSessionStatus)
		sessions.GET("/:id/status", h.GetSessionStatus)
		sessions.PUT("/:id/parties/:partyId/ready", h.MarkPartyReady)
		sessions.POST("/:id/start", h.StartSession)
		sessions.POST("/:id/complete", h.ReportCompletion)
		sessions.DELETE("/:id", h.CloseSession)
	}
}

// CreateSessionRequest is the HTTP request body for creating a session
type CreateSessionRequest struct {
	SessionType  string                    `json:"sessionType" binding:"required,oneof=keygen sign"`
	ThresholdN   int                       `json:"thresholdN" binding:"required,min=2,max=10"`
	ThresholdT   int                       `json:"thresholdT" binding:"required,min=1"`
	CreatedBy    string                    `json:"createdBy" binding:"required"`
	Participants []ParticipantInfoRequest  `json:"participants,omitempty"`
	MessageHash  string                    `json:"messageHash,omitempty"`
	ExpiresIn    int64                     `json:"expiresIn,omitempty"`
}

// ParticipantInfoRequest represents a participant in the request
type ParticipantInfoRequest struct {
	PartyID    string            `json:"party_id" binding:"required"`
	DeviceInfo DeviceInfoRequest `json:"device_info" binding:"required"`
}

// DeviceInfoRequest represents device info in the request
type DeviceInfoRequest struct {
	DeviceType string `json:"device_type" binding:"required"`
	DeviceID   string `json:"device_id,omitempty"`
	Platform   string `json:"platform,omitempty"`
	AppVersion string `json:"app_version,omitempty"`
}

// CreateSessionResponse is the HTTP response for creating a session
type CreateSessionResponse struct {
	SessionID string `json:"sessionId"`
	JoinToken string `json:"joinToken"`
	Status    string `json:"status"`
	ExpiresAt int64  `json:"expiresAt,omitempty"`
}

// CreateSession handles POST /sessions
func (h *SessionHTTPHandler) CreateSession(c *gin.Context) {
	var req CreateSessionRequest
	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 exceed threshold_n"})
		return
	}

	// Convert request to input
	participants := make([]input.ParticipantInfo, len(req.Participants))
	for i, p := range req.Participants {
		participants[i] = input.ParticipantInfo{
			PartyID: p.PartyID,
			DeviceInfo: entities.DeviceInfo{
				DeviceType: entities.DeviceType(p.DeviceInfo.DeviceType),
				DeviceID:   p.DeviceInfo.DeviceID,
				Platform:   p.DeviceInfo.Platform,
				AppVersion: p.DeviceInfo.AppVersion,
			},
		}
	}

	var messageHash []byte
	if req.MessageHash != "" {
		messageHash = []byte(req.MessageHash)
	}

	expiresIn := time.Duration(req.ExpiresIn) * time.Second
	if expiresIn == 0 {
		expiresIn = 24 * time.Hour // Default: 24-hour session validity
	}

	inputData := input.CreateSessionInput{
		InitiatorID:  req.CreatedBy,
		SessionType:  req.SessionType,
		ThresholdN:   req.ThresholdN,
		ThresholdT:   req.ThresholdT,
		Participants: participants,
		MessageHash:  messageHash,
		ExpiresIn:    expiresIn,
	}

	output, err := h.createSessionUC.Execute(c.Request.Context(), inputData)
	if err != nil {
		logger.Error("failed to create session", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Extract a single join token (for E2E compatibility)
	// In a real scenario with pre-registered participants, we'd return all tokens
	// For now, generate a universal join token
	joinToken := output.SessionID.String() // Use session ID as join token for simplicity
	if len(output.JoinTokens) > 0 {
		// If there are participant-specific tokens, use the first one
		for _, token := range output.JoinTokens {
			joinToken = token
			break
		}
	}

	c.JSON(http.StatusCreated, CreateSessionResponse{
		SessionID: output.SessionID.String(),
		JoinToken: joinToken,
		Status:    "created",
		ExpiresAt: output.ExpiresAt.UnixMilli(),
	})
}

// JoinSessionRequest is the HTTP request body for joining a session
type JoinSessionRequest struct {
	PartyID    string            `json:"party_id" binding:"required"`
	JoinToken  string            `json:"join_token" binding:"required"`
	DeviceInfo DeviceInfoRequest `json:"device_info" binding:"required"`
}

// JoinSessionResponse is the HTTP response for joining a session
type JoinSessionResponse struct {
	Success      bool               `json:"success"`
	SessionInfo  SessionInfoDTO     `json:"session_info"`
	OtherParties []PartyInfoDTO     `json:"other_parties"`
}

// SessionInfoDTO represents session info in responses
type SessionInfoDTO struct {
	SessionID   string `json:"session_id"`
	SessionType string `json:"session_type"`
	ThresholdN  int    `json:"threshold_n"`
	ThresholdT  int    `json:"threshold_t"`
	MessageHash string `json:"message_hash,omitempty"`
	Status      string `json:"status"`
}

// PartyInfoDTO represents party info in responses
type PartyInfoDTO struct {
	PartyID    string            `json:"party_id"`
	PartyIndex int               `json:"party_index"`
	DeviceInfo DeviceInfoRequest `json:"device_info"`
}

// JoinSessionByTokenRequest is the HTTP request body for joining by token
type JoinSessionByTokenRequest struct {
	JoinToken  string `json:"joinToken" binding:"required"`
	PartyID    string `json:"partyId" binding:"required"`
	DeviceType string `json:"deviceType" binding:"required"`
	DeviceID   string `json:"deviceId,omitempty"`
}

// JoinSessionByTokenResponse is the HTTP response for joining by token
type JoinSessionByTokenResponse struct {
	SessionID    string                      `json:"sessionId"`
	PartyIndex   int                         `json:"partyIndex"`
	Status       string                      `json:"status"`
	Participants []ParticipantStatusDTO      `json:"participants"`
}

// ParticipantStatusDTO represents participant status in responses
type ParticipantStatusDTO struct {
	PartyID    string `json:"partyId"`
	PartyIndex int    `json:"partyIndex"`
	Status     string `json:"status"`
}

// JoinSessionByToken handles POST /sessions/join (join by token without session ID)
func (h *SessionHTTPHandler) JoinSessionByToken(c *gin.Context) {
	var req JoinSessionByTokenRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		logger.Error("failed to bind join session request", zap.Error(err))
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Pass empty UUID - the use case will extract session ID from the JWT
	inputData := input.JoinSessionInput{
		SessionID: uuid.Nil,
		PartyID:   req.PartyID,
		JoinToken: req.JoinToken,
		DeviceInfo: entities.DeviceInfo{
			DeviceType: entities.DeviceType(req.DeviceType),
			DeviceID:   req.DeviceID,
		},
	}

	output, err := h.joinSessionUC.Execute(c.Request.Context(), inputData)
	if err != nil {
		logger.Error("failed to join session", zap.Error(err))
		// Return 401 for authentication/token errors
		if err.Error() == "invalid token" || err.Error() == "token expired" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
			return
		}
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Build participant status list
	myPartyIndex := output.PartyIndex

	participants := make([]ParticipantStatusDTO, 0)
	participants = append(participants, ParticipantStatusDTO{
		PartyID: req.PartyID,
		Status:  "joined",
	})
	for _, p := range output.OtherParties {
		participants = append(participants, ParticipantStatusDTO{
			PartyID: p.PartyID,
			Status:  "joined",
		})
	}

	c.JSON(http.StatusOK, JoinSessionByTokenResponse{
		SessionID:    output.SessionInfo.SessionID.String(),
		PartyIndex:   myPartyIndex,
		Status:       output.SessionInfo.Status,
		Participants: participants,
	})
}

// JoinSession handles POST /sessions/:id/join
func (h *SessionHTTPHandler) JoinSession(c *gin.Context) {
	sessionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session ID"})
		return
	}

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

	inputData := input.JoinSessionInput{
		SessionID: sessionID,
		PartyID:   req.PartyID,
		JoinToken: req.JoinToken,
		DeviceInfo: entities.DeviceInfo{
			DeviceType: entities.DeviceType(req.DeviceInfo.DeviceType),
			DeviceID:   req.DeviceInfo.DeviceID,
			Platform:   req.DeviceInfo.Platform,
			AppVersion: req.DeviceInfo.AppVersion,
		},
	}

	output, err := h.joinSessionUC.Execute(c.Request.Context(), inputData)
	if err != nil {
		logger.Error("failed to join session", zap.Error(err))
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	otherParties := make([]PartyInfoDTO, len(output.OtherParties))
	for i, p := range output.OtherParties {
		otherParties[i] = PartyInfoDTO{
			PartyID:    p.PartyID,
			PartyIndex: p.PartyIndex,
			DeviceInfo: DeviceInfoRequest{
				DeviceType: string(p.DeviceInfo.DeviceType),
				DeviceID:   p.DeviceInfo.DeviceID,
				Platform:   p.DeviceInfo.Platform,
				AppVersion: p.DeviceInfo.AppVersion,
			},
		}
	}

	c.JSON(http.StatusOK, JoinSessionResponse{
		Success: output.Success,
		SessionInfo: SessionInfoDTO{
			SessionID:   output.SessionInfo.SessionID.String(),
			SessionType: output.SessionInfo.SessionType,
			ThresholdN:  output.SessionInfo.ThresholdN,
			ThresholdT:  output.SessionInfo.ThresholdT,
			MessageHash: string(output.SessionInfo.MessageHash),
			Status:      output.SessionInfo.Status,
		},
		OtherParties: otherParties,
	})
}

// SessionStatusResponse is the HTTP response for session status
type SessionStatusResponse struct {
	SessionID    string                   `json:"sessionId"`
	Status       string                   `json:"status"`
	ThresholdT   int                      `json:"thresholdT"`
	ThresholdN   int                      `json:"thresholdN"`
	Participants []ParticipantStatusDTO   `json:"participants"`
	PublicKey    string                 `json:"publicKey,omitempty"`
	Signature    string                 `json:"signature,omitempty"`
}

// GetSessionStatus handles GET /sessions/:id/status
func (h *SessionHTTPHandler) GetSessionStatus(c *gin.Context) {
	sessionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session ID"})
		return
	}

	output, err := h.getSessionStatusUC.Execute(c.Request.Context(), sessionID)
	if err != nil {
		logger.Error("failed to get session status", zap.Error(err))
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}

	// Convert participants to DTO
	participants := make([]ParticipantStatusDTO, len(output.Participants))
	for i, p := range output.Participants {
		participants[i] = ParticipantStatusDTO{
			PartyID:    p.PartyID,
			PartyIndex: p.PartyIndex,
			Status:     p.Status,
		}
	}

	c.JSON(http.StatusOK, SessionStatusResponse{
		SessionID:    output.SessionID.String(),
		Status:       output.Status,
		ThresholdT:   output.ThresholdT,
		ThresholdN:   output.ThresholdN,
		Participants: participants,
		PublicKey:    string(output.PublicKey),
		Signature:    string(output.Signature),
	})
}

// ReportCompletionRequest is the HTTP request for reporting completion
type ReportCompletionRequest struct {
	PartyID   string `json:"party_id" binding:"required"`
	PublicKey string `json:"public_key,omitempty"`
	Signature string `json:"signature,omitempty"`
}

// ReportCompletion handles POST /sessions/:id/complete
func (h *SessionHTTPHandler) ReportCompletion(c *gin.Context) {
	sessionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session ID"})
		return
	}

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

	inputData := input.ReportCompletionInput{
		SessionID: sessionID,
		PartyID:   req.PartyID,
		PublicKey: []byte(req.PublicKey),
		Signature: []byte(req.Signature),
	}

	output, err := h.reportCompletionUC.Execute(c.Request.Context(), inputData)
	if err != nil {
		logger.Error("failed to report completion", zap.Error(err))
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"success":       output.Success,
		"all_completed": output.AllCompleted,
	})
}

// CloseSession handles DELETE /sessions/:id
func (h *SessionHTTPHandler) CloseSession(c *gin.Context) {
	sessionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session ID"})
		return
	}

	err = h.closeSessionUC.Execute(c.Request.Context(), sessionID)
	if err != nil {
		logger.Error("failed to close session", zap.Error(err))
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"success": true})
}

// MarkPartyReady handles PUT /sessions/:id/parties/:partyId/ready
func (h *SessionHTTPHandler) MarkPartyReady(c *gin.Context) {
	sessionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session ID"})
		return
	}

	partyID := c.Param("partyId")
	if partyID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "party ID is required"})
		return
	}

	logger.Info("marking party as ready", zap.String("session_id", sessionID.String()), zap.String("party_id", partyID))

	// Load session
	session, err := h.sessionRepo.FindByUUID(c.Request.Context(), sessionID)
	if err != nil {
		if err == entities.ErrSessionNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "session not found"})
			return
		}
		logger.Error("failed to load session", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load session"})
		return
	}

	// Create party ID value object
	partyIDVO, err := value_objects.NewPartyID(partyID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid party ID"})
		return
	}

	// Update participant status to ready
	if err := session.UpdateParticipantStatus(partyIDVO, value_objects.ParticipantStatusReady); err != nil {
		logger.Error("failed to mark party as ready", zap.Error(err))
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Save session
	if err := h.sessionRepo.Update(c.Request.Context(), session); err != nil {
		logger.Error("failed to save session", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save session"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"success": true})
}

// StartSession handles POST /sessions/:id/start
func (h *SessionHTTPHandler) StartSession(c *gin.Context) {
	sessionID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session ID"})
		return
	}

	logger.Info("starting session", zap.String("session_id", sessionID.String()))

	// Load session
	session, err := h.sessionRepo.FindByUUID(c.Request.Context(), sessionID)
	if err != nil {
		if err == entities.ErrSessionNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "session not found"})
			return
		}
		logger.Error("failed to load session", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load session"})
		return
	}

	// Start the session
	if err := session.Start(); err != nil {
		logger.Error("failed to start session", zap.Error(err))
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Save session
	if err := h.sessionRepo.Update(c.Request.Context(), session); err != nil {
		logger.Error("failed to save session", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save session"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"success": true})
}

// HealthCheck handles GET /health
func (h *SessionHTTPHandler) HealthCheck(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"status":  "healthy",
		"service": "session-coordinator",
		"time":    time.Now().UTC().Format(time.RFC3339),
	})
}
