package notify

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"

	commonLogic "github.com/perfect-panel/server/internal/logic/common"
	"github.com/perfect-panel/server/pkg/constant"

	"github.com/perfect-panel/server/pkg/xerr"
	"github.com/pkg/errors"

	"github.com/hibiken/asynq"
	"github.com/perfect-panel/server/internal/model/payment"
	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/pkg/logger"
	"github.com/perfect-panel/server/pkg/payment/alipay"
	"github.com/perfect-panel/server/queue/types"
)

type AlipayNotifyLogic struct {
	logger.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

// Alipay notify
func NewAlipayNotifyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AlipayNotifyLogic {
	return &AlipayNotifyLogic{
		Logger: logger.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *AlipayNotifyLogic) AlipayNotify(r *http.Request) error {
	data, ok := l.ctx.Value(constant.CtxKeyPayment).(*payment.Payment)
	if !ok {
		return fmt.Errorf("payment config not found")
	}
	var config payment.AlipayF2FConfig
	if err := json.Unmarshal([]byte(data.Config), &config); err != nil {
		l.Logger.Error("[AlipayNotify] Unmarshal config failed", logger.Field("error", err.Error()))
		return err
	}
	client := alipay.NewClient(alipay.Config{
		AppId:       config.AppId,
		PrivateKey:  config.PrivateKey,
		PublicKey:   config.PublicKey,
		InvoiceName: config.InvoiceName,
		NotifyURL:   data.Domain + "/v1/payment/alipay/notify",
	})
	notify, err := client.DecodeNotification(r.Form)
	if err != nil {
		l.Logger.Error("[AlipayNotify] Decode notification failed", logger.Field("error", err.Error()))
		return err
	}
	commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_notify_received",
		"[SubscriptionFlow] alipay notify received",
		logger.Field("order_no", notify.OrderNo),
		logger.Field("payment_platform", data.Platform),
		logger.Field("notify_status", string(notify.Status)),
	)
	if notify.Status == alipay.Success {
		orderInfo, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, notify.OrderNo)
		if err != nil {
			l.Logger.Error("[AlipayNotify] Find order failed", logger.Field("error", err.Error()), logger.Field("orderNo", notify.OrderNo))
			return errors.Wrapf(xerr.NewErrCode(xerr.OrderNotExist), "order not exist: %v", notify.OrderNo)
		}

		if orderInfo.Status == 5 {
			return nil
		}

		// Update order status
		err = l.svcCtx.OrderModel.UpdateOrderStatus(l.ctx, notify.OrderNo, 2)
		if err != nil {
			l.Logger.Error("[AlipayNotify] Update order status failed", logger.Field("error", err.Error()), logger.Field("orderNo", notify.OrderNo))
			return err
		}
		commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "payment_settled",
			"[SubscriptionFlow] alipay notify marked order as paid",
			append(commonLogic.OrderTraceFields(orderInfo),
				logger.Field("payment_platform", data.Platform),
			)...,
		)
		l.Logger.Info("[AlipayNotify] Notify status success", logger.Field("orderNo", notify.OrderNo))
		payload := types.ForthwithActivateOrderPayload{
			OrderNo: notify.OrderNo,
		}
		bytes, err := json.Marshal(&payload)
		if err != nil {
			l.Logger.Error("[AlipayNotify] Marshal payload failed", logger.Field("error", err.Error()))
			return err
		}
		task := asynq.NewTask(types.ForthwithActivateOrder, bytes, asynq.MaxRetry(5))
		taskInfo, err := l.svcCtx.Queue.EnqueueContext(l.ctx, task)
		if err != nil {
			l.Logger.Error("[AlipayNotify] Enqueue task failed", logger.Field("error", err.Error()))
			return err
		}
		commonLogic.SubscriptionTraceInfo(l.Logger, commonLogic.SubscriptionTraceFlowOrder, "activation_task_enqueued",
			"[SubscriptionFlow] activation task enqueued from alipay notify",
			append(commonLogic.OrderTraceFields(orderInfo),
				logger.Field("payment_platform", data.Platform),
				logger.Field("queue_task_id", taskInfo.ID),
			)...,
		)
		l.Logger.Info("[AlipayNotify] Enqueue task success", logger.Field("taskInfo", taskInfo))
	} else {
		l.Logger.Error("[AlipayNotify] Notify status failed", logger.Field("status", string(notify.Status)))
	}
	return nil
}
