package handlers

import (

	"github.com/gin-gonic/gin"
	"github.com/jackc/pgx/v5/pgxpool"
	"ticketing-backend/internal/services"
	"ticketing-backend/internal/utils"
)

type OrderHandler struct {
	pool         *pgxpool.Pool
	orderService *services.OrderService
	promoService *services.PromoService
}

func NewOrderHandler(pool *pgxpool.Pool, orderService *services.OrderService, promoService *services.PromoService) *OrderHandler {
	return &OrderHandler{pool: pool, orderService: orderService, promoService: promoService}
}

func (h *OrderHandler) CreateLock(c *gin.Context) {
	userID, _ := c.Get("userId")
	userIDStr, _ := userID.(string)

	var req struct {
		EventID       string `json:"eventId" binding:"required"`
		TicketClassID string `json:"ticketClassId" binding:"required"`
		Quantity      int    `json:"quantity" binding:"required,min=1,max=10"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		utils.BadRequest(c, "Invalid request")
		return
	}

	lock, err := h.orderService.CreateOrderLock(req.EventID, req.TicketClassID, req.Quantity, userIDStr)
	if err != nil {
		utils.BadRequest(c, err.Error())
		return
	}

	utils.Success(c, gin.H{"lock": lock})
}

func (h *OrderHandler) GetOrder(c *gin.Context) {
	orderID := c.Param("orderId")
	_, _ = c.Get("userId")

	type OrderDetail struct {
		ID              string   `json:"id"`
		EventID         string   `json:"event_id"`
		TicketClassID   string   `json:"ticket_class_id"`
		Quantity        int      `json:"quantity"`
		TotalAmount     float64  `json:"total_amount"`
		BuyerName       string   `json:"buyer_name"`
		BuyerEmail      string   `json:"buyer_email"`
		BuyerPhone      string   `json:"buyer_phone"`
		Status          string   `json:"status"`
		EventName       *string  `json:"event_name"`
		EventDate       *string  `json:"event_date"`
		Venue           *string  `json:"venue"`
		TicketClassName *string  `json:"ticket_class_name"`
		CreatedAt       string   `json:"created_at"`
		DiscountAmount  *float64 `json:"discount_amount"`
		OriginalAmount  *float64 `json:"original_amount"`
	}

	var order OrderDetail
	var createdAt string
	err := h.pool.QueryRow(c.Request.Context(),
		`SELECT o.id, o.event_id, o.ticket_class_id, o.quantity, o.total_amount,
			o.buyer_name, o.buyer_email, o.buyer_phone, o.status,
			e.name, e.event_date, e.venue, tc.name,
			o.created_at, o.discount_amount, o.original_amount
		 FROM orders o
		 LEFT JOIN events e ON o.event_id = e.id
		 LEFT JOIN ticket_classes tc ON o.ticket_class_id = tc.id
		 WHERE o.id = $1`, orderID,
	).Scan(&order.ID, &order.EventID, &order.TicketClassID, &order.Quantity, &order.TotalAmount,
		&order.BuyerName, &order.BuyerEmail, &order.BuyerPhone, &order.Status,
		&order.EventName, &order.EventDate, &order.Venue, &order.TicketClassName,
		&createdAt, &order.DiscountAmount, &order.OriginalAmount)

	if err != nil {
		utils.NotFound(c, "Order not found")
		return
	}

	order.CreatedAt = createdAt

	var payment *gin.H
	row := h.pool.QueryRow(c.Request.Context(),
		`SELECT id, status, amount, gateway, gateway_transaction_id, snap_token,
		        payment_url, expires_at, created_at
		 FROM payments WHERE order_id = $1 ORDER BY created_at DESC LIMIT 1`, orderID)

	var pID, pStatus, pGateway string
	var pAmount float64
	var pGTID, pSnapToken, pPaymentURL, pCreatedAt *string
	var pExpiresAt *string
	err = row.Scan(&pID, &pStatus, &pAmount, &pGateway, &pGTID, &pSnapToken, &pPaymentURL, &pExpiresAt, &pCreatedAt)
	if err == nil {
		payment = &gin.H{
			"id": pID, "status": pStatus, "amount": pAmount, "gateway": pGateway,
			"gatewayTransactionId": pGTID, "snapToken": pSnapToken,
			"paymentUrl": pPaymentURL, "expiresAt": pExpiresAt, "createdAt": pCreatedAt,
		}
	}

	utils.Success(c, gin.H{
		"order":   order,
		"payment": payment,
	})
}

func (h *OrderHandler) CancelOrder(c *gin.Context) {
	orderID := c.Param("orderId")
	userID, _ := c.Get("userId")
	userIDStr, _ := userID.(string)

	var ownerID string
	err := h.pool.QueryRow(c.Request.Context(),
		`SELECT buyer_id FROM orders WHERE id = $1`, orderID,
	).Scan(&ownerID)
	if err != nil || ownerID != userIDStr {
		utils.Forbidden(c, "Order not found or not owned by you")
		return
	}

	if err := h.orderService.CancelOrder(orderID); err != nil {
		utils.InternalError(c, "Failed to cancel order")
		return
	}

	utils.Success(c, gin.H{"message": "Order cancelled"})
}

func (h *OrderHandler) GetLockInfo(c *gin.Context) {
	orderID := c.Query("orderId")
	if orderID == "" {
		utils.BadRequest(c, "orderId is required")
		return
	}

	var lockID, status string
	err := h.pool.QueryRow(c.Request.Context(),
		`SELECT o.order_lock_id, o.status FROM orders o WHERE o.id = $1`, orderID,
	).Scan(&lockID, &status)

	if err != nil || lockID == "" {
		utils.NotFound(c, "No lock found for this order")
		return
	}

	utils.Success(c, gin.H{"lockId": lockID, "orderStatus": status})
}

func (h *OrderHandler) ConvertLockToOrder(c *gin.Context) {
	userID, _ := c.Get("userId")
	userIDStr, _ := userID.(string)

	var req struct {
		LockID         string  `json:"lockId" binding:"required"`
		BuyerName      string  `json:"buyerName" binding:"required"`
		BuyerEmail     string  `json:"buyerEmail" binding:"required"`
		BuyerPhone     string  `json:"buyerPhone" binding:"required"`
		PromoCode      *string `json:"promoCode,omitempty"`
		UseDomainPromo *bool   `json:"useDomainPromo,omitempty"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		utils.BadRequest(c, "Invalid request: "+err.Error())
		return
	}

	var promoID, domainPromoID *string
	var discountAmount, originalAmount *float64

	if req.PromoCode != nil && *req.PromoCode != "" {
		var eventID string
		var tcID string
		var qty int
		err := h.pool.QueryRow(c.Request.Context(),
			`SELECT l.event_id, l.ticket_class_id, l.quantity_locked
			 FROM order_locks l WHERE l.id = $1`, req.LockID,
		).Scan(&eventID, &tcID, &qty)
		if err == nil {
			var price float64
			h.pool.QueryRow(c.Request.Context(),
				`SELECT price FROM ticket_classes WHERE id = $1`, tcID,
			).Scan(&price)
			baseAmount := price * float64(qty)
			originalAmount = &baseAmount

			validation, err := h.promoService.ValidatePromoCode(*req.PromoCode, userIDStr, req.BuyerEmail, eventID, baseAmount)
			if err != nil || !validation.Valid {
				if validation != nil && validation.Message != nil {
					utils.BadRequest(c, *validation.Message)
				} else {
					utils.BadRequest(c, "Invalid promo code")
				}
				return
			}

			if validation.Promo != nil {
				promoID = &validation.Promo.ID
			}
			if validation.DomainPromo != nil {
				domainPromoID = &validation.DomainPromo.ID
			}
			discountAmount = &validation.DiscountAmount
			originalAmount = &validation.FinalAmount
		}
	}

	if req.UseDomainPromo != nil && *req.UseDomainPromo {
		var eventID, tcID string
		var qty int
		h.pool.QueryRow(c.Request.Context(),
			`SELECT l.event_id, l.ticket_class_id, l.quantity_locked FROM order_locks l WHERE l.id = $1`,
			req.LockID,
		).Scan(&eventID, &tcID, &qty)

		var price float64
		h.pool.QueryRow(c.Request.Context(), `SELECT price FROM ticket_classes WHERE id = $1`, tcID).Scan(&price)
		baseAmount := price * float64(qty)
		if originalAmount == nil {
			originalAmount = &baseAmount
		}

		validation, err := h.promoService.CheckDomainPromo(req.BuyerEmail, eventID, baseAmount)
		if err == nil && validation.Valid {
			if validation.DomainPromo != nil {
				domainPromoID = &validation.DomainPromo.ID
			}
			discountAmount = &validation.DiscountAmount
		}
	}

	order, err := h.orderService.ConvertLockToOrder(
		req.LockID, req.BuyerName, req.BuyerEmail, req.BuyerPhone,
		promoID, domainPromoID, discountAmount, originalAmount,
	)
	if err != nil {
		utils.BadRequest(c, err.Error())
		return
	}

	if promoID != nil || domainPromoID != nil {
		discAmt := float64(0)
		if discountAmount != nil {
			discAmt = *discountAmount
		}
		h.promoService.RecordUsage(promoID, domainPromoID, &userIDStr, &order.ID, discAmt)
	}

	utils.Success(c, gin.H{"order": order})
}
