package handlers

import (
	"net/http"
	"strconv"
	"strings"

	"payment/internal/models"
	"payment/internal/services"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/sirupsen/logrus"
	"gorm.io/gorm"
)

// PaymentHandler handles payment-related HTTP requests
type PaymentHandler struct {
	paymentService *services.PaymentService
	db             *gorm.DB
}

// NewPaymentHandler creates a new payment handler
func NewPaymentHandler(paymentService *services.PaymentService, db *gorm.DB) *PaymentHandler {
	return &PaymentHandler{
		paymentService: paymentService,
		db:             db,
	}
}

// validateClient validates if a client exists and is active
func (h *PaymentHandler) validateClient(clientID uuid.UUID) error {
	var client models.Client
	if err := h.db.Where("id = ? AND is_active = true", clientID).First(&client).Error; err != nil {
		return err
	}
	return nil
}

// InitiatePayment initiates a payment (public endpoint)
func (h *PaymentHandler) InitiatePayment(c *gin.Context) {
	var req struct {
		ClientID    string `json:"client_id" binding:"required"`
		PackageID   string `json:"package_id" binding:"required"`
		PhoneNumber string `json:"phone_number" binding:"required"`
		Description string `json:"description" binding:"required"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Missing required fields: client_id, package_id, phone_number, description"})
		return
	}

	// Parse UUIDs
	clientID, err := uuid.Parse(req.ClientID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid client_id format"})
		return
	}

	packageID, err := uuid.Parse(req.PackageID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid package_id format"})
		return
	}

	// Validate client exists and is active
	if err := h.validateClient(clientID); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": "Invalid or inactive client"})
		return
	}

	// Initiate payment
	response, err := h.paymentService.InitiatePaymentPublic(c.Request.Context(), clientID, packageID, req.PhoneNumber, req.Description)
	if err != nil {
		logrus.WithError(err).Error("Failed to initiate payment")
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, response)
}

// CheckPaymentStatus checks payment status (public endpoint)
func (h *PaymentHandler) CheckPaymentStatus(c *gin.Context) {
	var req struct {
		ClientID   string `json:"client_id" binding:"required"`
		CheckoutID string `json:"checkout_id" binding:"required"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Missing required fields: client_id, checkout_id"})
		return
	}

	// Parse client ID
	clientID, err := uuid.Parse(req.ClientID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid client_id format"})
		return
	}

	// Validate client exists and is active
	if err := h.validateClient(clientID); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": "Invalid or inactive client"})
		return
	}

	// Check payment status
	response, err := h.paymentService.CheckPaymentStatus(c.Request.Context(), clientID, req.CheckoutID)
	if err != nil {
		logrus.WithError(err).Error("Failed to check payment status")

		// Determine appropriate status code
		statusCode := http.StatusInternalServerError
		errorMessage := "internal server error"

		if err.Error() != "" {
			if strings.Contains(err.Error(), "Safaricom query failed with status 500") ||
				strings.Contains(err.Error(), "Safaricom query failed with status 429") {
				statusCode = http.StatusBadGateway
				errorMessage = err.Error()
			} else if strings.Contains(err.Error(), "client not found") {
				statusCode = http.StatusForbidden
				errorMessage = "invalid or inactive client"
			} else {
				errorMessage = err.Error()
			}
		}

		c.JSON(statusCode, gin.H{"error": errorMessage})
		return
	}

	c.JSON(http.StatusOK, response)
}

// GetTransactions retrieves transactions for a client
func (h *PaymentHandler) GetTransactions(c *gin.Context) {
	// Get client ID from query parameter
	clientIDStr := c.Query("client_id")
	if clientIDStr == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "client_id query parameter is required"})
		return
	}

	clientID, err := uuid.Parse(clientIDStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid client_id format"})
		return
	}

	// Validate client exists and is active
	if err := h.validateClient(clientID); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": "Invalid or inactive client"})
		return
	}

	// Parse query parameters
	limitStr := c.DefaultQuery("limit", "50")
	offsetStr := c.DefaultQuery("offset", "0")

	limit, err := strconv.Atoi(limitStr)
	if err != nil || limit <= 0 || limit > 1000 {
		limit = 50
	}

	offset, err := strconv.Atoi(offsetStr)
	if err != nil || offset < 0 {
		offset = 0
	}

	// Get transactions
	transactions, err := h.paymentService.GetTransactions(c.Request.Context(), clientID, limit, offset)
	if err != nil {
		logrus.WithError(err).Error("Failed to get transactions")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get transactions"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"transactions": transactions,
		"limit":        limit,
		"offset":       offset,
		"count":        len(transactions),
	})
}

// GetTransaction retrieves a specific transaction
func (h *PaymentHandler) GetTransaction(c *gin.Context) {
	transactionIDStr := c.Param("id")
	transactionID, err := uuid.Parse(transactionIDStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid transaction ID"})
		return
	}

	// Get client ID from query parameter
	clientIDStr := c.Query("client_id")
	if clientIDStr == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "client_id query parameter is required"})
		return
	}

	clientID, err := uuid.Parse(clientIDStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid client_id format"})
		return
	}

	// Validate client exists and is active
	if err := h.validateClient(clientID); err != nil {
		c.JSON(http.StatusForbidden, gin.H{"error": "Invalid or inactive client"})
		return
	}

	// Get transaction
	var transaction models.Transaction
	if err := h.db.Where("id = ? AND client_id = ?", transactionID, clientID).First(&transaction).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "Transaction not found"})
			return
		}
		logrus.WithError(err).Error("Failed to get transaction")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get transaction"})
		return
	}

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

// MpesaCallback handles M-Pesa callback
func (h *PaymentHandler) MpesaCallback(c *gin.Context) {
	var callbackData map[string]interface{}
	if err := c.ShouldBindJSON(&callbackData); err != nil {
		logrus.WithError(err).Error("Failed to bind callback data")
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid callback data"})
		return
	}

	// Extract client ID from callback (this would need to be determined based on your callback URL structure)
	// For now, we'll use a placeholder
	clientID := uuid.New() // This should be extracted from the callback URL or callback data

	// Process callback
	if err := h.paymentService.ProcessMpesaCallback(c.Request.Context(), clientID, callbackData); err != nil {
		logrus.WithError(err).Error("Failed to process M-Pesa callback")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to process callback"})
		return
	}

	// Return success response to M-Pesa
	c.JSON(http.StatusOK, gin.H{
		"ResultCode": 0,
		"ResultDesc": "Success",
	})
}
