package handler

import (
	"io"
	"kra/internal/service"
	"kra/internal/service/dto"
	"net/http"

	"github.com/gin-gonic/gin"
)

type Payment struct{ service *service.PaymentService }

func NewPayment(service *service.PaymentService) *Payment { return &Payment{service: service} }
func (h *Payment) Order(c *gin.Context) {
	var req dto.PaymentQueryRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	order, err := h.service.Order(c.Request.Context(), req.Provider, req.TradeNo)
	if err != nil {
		Fail(c, err.Error())
		return
	}
	OKWithData(c, order)
}

func (h *Payment) OrderByPath(c *gin.Context) {
	order, err := h.service.Order(c.Request.Context(), c.Param("provider"), c.Param("tradeNo"))
	if err != nil {
		Fail(c, err.Error())
		return
	}
	OKWithData(c, order)
}

func (h *Payment) RefundByPath(c *gin.Context) {
	var req struct {
		Amount int64 `json:"amount"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	result, err := h.service.Refund(c.Request.Context(), &dto.PaymentRefundRequest{
		Provider: c.Param("provider"), TradeNo: c.Param("tradeNo"), Amount: req.Amount,
	})
	if err != nil {
		Fail(c, err.Error())
		return
	}
	OKWithData(c, result)
}

func (h *Payment) FulfillByPath(c *gin.Context) {
	result, err := h.service.Fulfill(c.Request.Context(), &dto.PaymentFulfillRequest{
		Provider: c.Param("provider"), TradeNo: c.Param("tradeNo"),
	})
	if err != nil {
		Fail(c, err.Error())
		return
	}
	OKWithData(c, result)
}

func (h *Payment) Orders(c *gin.Context) {
	var req dto.PaymentOrderListRequest
	if err := c.ShouldBindQuery(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	items, total, page, pageSize, err := h.service.Orders(c.Request.Context(), &req)
	if err != nil {
		Fail(c, err.Error())
		return
	}
	Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: page, PageSize: pageSize}, "获取成功")
}

func (h *Payment) TestProvider(c *gin.Context) {
	result, err := h.service.TestProvider(c.Request.Context(), c.Param("provider"))
	if err != nil {
		if result != nil {
			Write(c, CodeError, result, err.Error())
			return
		}
		Fail(c, err.Error())
		return
	}
	OKWithData(c, result)
}
func (h *Payment) Create(c *gin.Context) {
	var req dto.PaymentRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	req.ClientIP = c.ClientIP()
	result, err := h.service.Create(c.Request.Context(), &req)
	if err != nil {
		Fail(c, err.Error())
		return
	}
	OKWithData(c, result)
}
func (h *Payment) Query(c *gin.Context) {
	var req dto.PaymentQueryRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	result, err := h.service.Query(c.Request.Context(), &req)
	if err != nil {
		Fail(c, err.Error())
		return
	}
	OKWithData(c, result)
}
func (h *Payment) Refund(c *gin.Context) {
	var req dto.PaymentRefundRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	result, err := h.service.Refund(c.Request.Context(), &req)
	if err != nil {
		Fail(c, err.Error())
		return
	}
	OKWithData(c, result)
}

func (h *Payment) Fulfill(c *gin.Context) {
	var req dto.PaymentFulfillRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	result, err := h.service.Fulfill(c.Request.Context(), &req)
	if err != nil {
		Fail(c, err.Error())
		return
	}
	OKWithData(c, result)
}
func (h *Payment) Callback(c *gin.Context) {
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 8<<20)
	raw, err := io.ReadAll(c.Request.Body)
	if err != nil {
		writePaymentCallbackAck(c, service.DefaultPaymentCallbackAck(c.Param("provider"), false))
		return
	}
	headers := map[string]string{}
	for k, v := range c.Request.Header {
		if len(v) > 0 {
			headers[k] = v[0]
		}
	}
	query := map[string]string{}
	for k, v := range c.Request.URL.Query() {
		if len(v) > 0 {
			query[k] = v[0]
		}
	}
	ack, err := h.service.Callback(c.Request.Context(), &dto.PaymentCallbackRequest{Provider: c.Param("provider"), Headers: headers, Body: raw, Query: query})
	writePaymentCallbackAck(c, ack)
	_ = err
}

func writePaymentCallbackAck(c *gin.Context, ack dto.PaymentCallbackAck) {
	if ack.StatusCode == 0 {
		ack.StatusCode = http.StatusInternalServerError
	}
	if ack.ContentType != "" {
		c.Header("Content-Type", ack.ContentType)
	}
	c.Status(ack.StatusCode)
	if len(ack.Body) > 0 {
		_, _ = c.Writer.Write(ack.Body)
	}
}
