package http

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"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/value_objects"
)

// AccountHTTPHandler handles HTTP requests for accounts
type AccountHTTPHandler struct {
	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
}

// NewAccountHTTPHandler creates a new AccountHTTPHandler
func NewAccountHTTPHandler(
	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,
) *AccountHTTPHandler {
	return &AccountHTTPHandler{
		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,
	}
}

// RegisterRoutes registers HTTP routes
func (h *AccountHTTPHandler) RegisterRoutes(router *gin.RouterGroup) {
	accounts := router.Group("/accounts")
	{
		accounts.POST("", h.CreateAccount)
		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)
	}

	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)
	}
}

// CreateAccountRequest represents the request for creating an account
type CreateAccountRequest struct {
	Username        string        `json:"username" binding:"required"`
	Email           string        `json:"email" binding:"required,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,
		}
	}

	output, err := h.createAccountUC.Execute(c.Request.Context(), ports.CreateAccountInput{
		Username:        req.Username,
		Email:           req.Email,
		Phone:           req.Phone,
		PublicKey:       []byte(req.PublicKey),
		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":   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
	}

	output, err := h.loginUC.Execute(c.Request.Context(), ports.LoginInput{
		Username:  req.Username,
		Challenge: []byte(req.Challenge),
		Signature: []byte(req.Signature),
	})
	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,
		}
	}

	output, err := h.completeRecoveryUC.Execute(c.Request.Context(), ports.CompleteRecoveryInput{
		RecoverySessionID:  id,
		NewPublicKey:       []byte(req.NewPublicKey),
		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"})
}
