package payment

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	bizpayment "kra/internal/biz/payment"
	"strings"

	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/douyin"
)

type douyinAdapter struct{}

func (a *douyinAdapter) client(c map[string]any) (*douyin.Client, error) {
	client, err := douyin.NewClient(text(c, "merchant_id"), text(c, "serial_no"), text(c, "api_key"), text(c, "private_key"))
	if err != nil {
		return nil, err
	}
	platformSerial := firstAny(c, "platform_serial_no", "platform_cert_serial")
	if cert := text(c, "platform_cert"); cert != "" {
		if platformSerial == "" {
			return nil, errors.New("抖音支付缺少 platform_serial_no")
		}
		if err := client.SetPlatformCert([]byte(cert), platformSerial); err != nil {
			return nil, err
		}
	}
	if proxy := text(c, "proxy_url"); proxy != "" {
		client.SetProxyHost(proxy)
	}
	return client, nil
}

func (a *douyinAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("抖音支付下单请求为空")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{}
	bm.Set("appid", text(c, "app_id"))
	bm.Set("description", req.Subject)
	bm.Set("out_trade_no", req.TradeNo)
	bm.Set("notify_url", req.NotifyURL)
	bm.SetBodyMap("amount", func(amount gopay.BodyMap) {
		amount.Set("total", req.Amount).Set("currency", strings.ToUpper(req.Currency))
	})
	mergeGoPayExtras(bm, req.Extra, "appid", "description", "out_trade_no", "notify_url", "amount", "mchid", "method", "pay_method", "trade_type", "channel")
	tradeType, err := douyinCreateMethod(req.Extra, c)
	if err != nil {
		return nil, err
	}
	switch tradeType {
	case "app":
		rsp, callErr := client.AppOrder(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if rsp == nil || rsp.Response == nil || rsp.Code != douyin.Success {
			return nil, douyinError(rsp)
		}
		return douyinPrepayResult(client, text(c, "app_id"), req.TradeNo, rsp.Response.PrepayId, true)
	case "h5":
		rsp, callErr := client.H5Order(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if rsp == nil || rsp.Response == nil || rsp.Code != douyin.Success {
			return nil, douyinError(rsp)
		}
		if strings.TrimSpace(rsp.Response.H5Url) == "" {
			return nil, errors.New("抖音支付 H5 下单响应缺少 h5_url")
		}
		payload, _ := json.Marshal(rsp.Response)
		return &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil
	case "native":
		rsp, callErr := client.NativeOrder(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if rsp == nil || rsp.Response == nil || rsp.Code != douyin.Success {
			return nil, douyinError(rsp)
		}
		if strings.TrimSpace(rsp.Response.CodeUrl) == "" {
			return nil, errors.New("抖音支付 Native 下单响应缺少 code_url")
		}
		payload, _ := json.Marshal(rsp.Response)
		return &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil
	case "jsapi":
		rsp, callErr := client.JsapiOrder(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if rsp == nil || rsp.Response == nil || rsp.Code != douyin.Success {
			return nil, douyinError(rsp)
		}
		return douyinPrepayResult(client, text(c, "app_id"), req.TradeNo, rsp.Response.PrepayId, false)
	default:
		return nil, fmt.Errorf("抖音支付不支持的下单方式: %s", tradeType)
	}
}

func douyinCreateMethod(extra, config map[string]any) (string, error) {
	value := firstAny(extra, "method", "pay_method", "trade_type", "pay_type", "channel")
	if value == "" {
		value = firstAny(config, "method", "pay_method", "trade_type", "pay_type", "channel")
	}
	normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
	switch normalized {
	case "", "jsapi", "js_api", "mini", "mini_program", "miniprogram", "applet":
		return "jsapi", nil
	case "app", "app_pay", "apporder":
		return "app", nil
	case "h5", "h5_pay":
		return "h5", nil
	case "native", "qr", "qrcode":
		return "native", nil
	default:
		return "", fmt.Errorf("抖音支付不支持的下单方式: %s", value)
	}
}

func douyinPrepayResult(client *douyin.Client, appID, tradeNo, prepayID string, app bool) (*bizpayment.PaymentResult, error) {
	if client == nil || strings.TrimSpace(prepayID) == "" {
		return nil, errors.New("抖音支付下单响应缺少 prepay_id")
	}
	var (
		params any
		err    error
	)
	if app {
		params, err = client.PaySignOfApp(appID, prepayID)
	} else {
		params, err = client.PaySignOfJSAPI(appID, prepayID)
	}
	if err != nil {
		return nil, fmt.Errorf("生成抖音支付客户端调起参数: %w", err)
	}
	return &bizpayment.PaymentResult{
		Provider: bizpayment.PaymentDouyin,
		Status:   "client_pending",
		TradeNo:  tradeNo,
		Payload:  mustJSON(params),
	}, nil
}

func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) {
	tradeNo = strings.TrimSpace(tradeNo)
	if tradeNo == "" {
		return nil, errors.New("抖音支付查单缺少 out_trade_no")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	rsp, err := client.OrderQueryByOutTradeNo(ctx, tradeNo)
	if err != nil {
		return nil, err
	}
	if rsp == nil || rsp.Response == nil || rsp.Code != douyin.Success {
		return nil, douyinError(rsp)
	}
	order := rsp.Response
	if err = validateDouyinMerchantIdentity(order.Mchid, order.Appid, c, false); err != nil {
		return nil, err
	}
	payload := mustJSON(order)
	var signedBody []byte
	if rsp.SignInfo != nil {
		signedBody = []byte(rsp.SignInfo.SignBody)
		if json.Valid(signedBody) {
			payload = append([]byte(nil), signedBody...)
		}
	}
	result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: normalizeDouyinState(order.TradeState), TradeNo: order.OutTradeNo, ProviderTradeNo: order.TransactionId, Payload: payload}
	if result.TradeNo == "" {
		result.TradeNo = tradeNo
	} else if result.TradeNo != tradeNo {
		return nil, errors.New("抖音支付查单响应的 out_trade_no 不匹配")
	}
	if err = populateDouyinPaymentAmount(result, order.Amount, signedBody); err != nil {
		return nil, err
	}
	return result, nil
}

func (a *douyinAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("抖音支付退款请求为空")
	}
	tradeNo := strings.TrimSpace(req.TradeNo)
	if tradeNo == "" {
		return nil, errors.New("抖音支付退款请求缺少 out_trade_no")
	}
	refundNo := strings.TrimSpace(req.RefundNo)
	if refundNo == "" {
		return nil, errors.New("抖音支付退款请求缺少 out_refund_no")
	}
	if req.Amount <= 0 || req.TotalAmount <= 0 || req.Amount > req.TotalAmount {
		return nil, errors.New("抖音支付退款金额无效")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{}
	bm.Set("out_trade_no", tradeNo).Set("out_refund_no", refundNo)
	bm.SetBodyMap("amount", func(m gopay.BodyMap) {
		m.Set("refund", req.Amount).Set("total", req.TotalAmount).Set("currency", strings.ToUpper(req.Currency))
	})
	mergeGoPayConfigExtras(bm, c, "refund_extra", "out_trade_no", "out_refund_no", "amount", "mchid")
	rsp, err := client.Refund(ctx, bm)
	if err != nil {
		return nil, err
	}
	if rsp == nil || rsp.Response == nil || rsp.Code != douyin.Success {
		return nil, douyinError(rsp)
	}
	r := rsp.Response
	refundID, err := validateDouyinRefundIdentity(r, tradeNo, refundNo)
	if err != nil {
		return nil, err
	}
	result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: normalizeRefundState(r.Status), TradeNo: tradeNo, ProviderTradeNo: refundID, Payload: mustJSON(r)}
	if r.Amount == nil {
		return nil, errors.New("抖音支付退款响应缺少金额")
	}
	result.Amount = int64(r.Amount.Refund)
	result.Currency = strings.ToUpper(strings.TrimSpace(r.Amount.Currency))
	if result.Amount != req.Amount {
		return nil, errors.New("抖音支付退款响应金额不匹配")
	}
	if result.Currency == "" {
		return nil, errors.New("抖音支付退款响应缺少币种")
	}
	if req.Currency != "" && !strings.EqualFold(result.Currency, req.Currency) {
		return nil, errors.New("抖音支付退款响应币种不匹配")
	}
	return result, nil
}

func validateDouyinRefundIdentity(refund *douyin.Refund, tradeNo, refundNo string) (string, error) {
	if refund == nil {
		return "", errors.New("抖音支付退款响应缺少退款单")
	}
	tradeNo = strings.TrimSpace(tradeNo)
	if tradeNo == "" {
		return "", errors.New("抖音支付退款请求缺少 out_trade_no")
	}
	refundNo = strings.TrimSpace(refundNo)
	if refundNo == "" {
		return "", errors.New("抖音支付退款请求缺少 out_refund_no")
	}
	returnedTradeNo := strings.TrimSpace(refund.OutTradeNo)
	if returnedTradeNo == "" {
		return "", errors.New("抖音支付退款响应缺少 out_trade_no")
	}
	if returnedTradeNo != tradeNo {
		return "", errors.New("抖音支付退款响应的 out_trade_no 不匹配")
	}
	returnedRefundNo := strings.TrimSpace(refund.OutRefundNo)
	if returnedRefundNo == "" {
		return "", errors.New("抖音支付退款响应缺少 out_refund_no")
	}
	if returnedRefundNo != refundNo {
		return "", errors.New("抖音支付退款响应的 out_refund_no 不匹配")
	}
	refundID := strings.TrimSpace(refund.RefundId)
	if refundID == "" {
		return "", errors.New("抖音支付退款响应缺少 refund_id")
	}
	return refundID, nil
}

func (a *douyinAdapter) Callback(ctx context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) {
	if callback == nil {
		return nil, errors.New("抖音支付回调为空")
	}
	request, err := callbackRequest(callback)
	if err != nil {
		return nil, err
	}
	notify, err := douyin.ParseNotify(request)
	if err != nil {
		return nil, err
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	if cert := text(c, "platform_cert"); cert == "" {
		return nil, errors.New("抖音支付回调缺少 platform_cert")
	}
	if err := notify.VerifySignByPKMap(client.PlatformCertMap()); err != nil {
		return nil, err
	}
	if notify.Resource != nil && strings.TrimSpace(notify.Resource.Mchid) != "" {
		if err = validateDouyinMerchantIdentity(notify.Resource.Mchid, "", c, false); err != nil {
			return nil, err
		}
	}
	if strings.EqualFold(notify.EventType, douyin.EventTransactionSuccess) {
		pay, err := notify.DecryptPayCipherText(text(c, "api_key"))
		if err != nil {
			return nil, err
		}
		if err = validateDouyinMerchantIdentity(pay.Mchid, pay.Appid, c, true); err != nil {
			return nil, err
		}
		plain, err := douyinNotifyPlaintext(notify, text(c, "api_key"))
		if err != nil {
			return nil, err
		}
		result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: normalizeDouyinState(pay.TradeState), TradeNo: pay.OutTradeNo, ProviderTradeNo: pay.TransactionId, Payload: append([]byte(nil), plain...), EventID: notify.Id}
		if err = populateDouyinPaymentAmount(result, pay.Amount, plain); err != nil {
			return nil, err
		}
		return result, nil
	}
	if strings.HasPrefix(strings.ToUpper(notify.EventType), "REFUND.") {
		refund, err := notify.DecryptRefundCipherText(text(c, "api_key"))
		if err != nil {
			return nil, err
		}
		if strings.TrimSpace(refund.Mchid) != "" {
			if err = validateDouyinMerchantIdentity(refund.Mchid, "", c, false); err != nil {
				return nil, err
			}
		}
		status := "pending"
		if strings.EqualFold(refund.RefundStatus, douyin.RefundStatusSuccess) {
			status = "success"
		} else if strings.EqualFold(refund.RefundStatus, douyin.RefundStatusClosed) || strings.EqualFold(refund.RefundStatus, douyin.RefundStatusAbnormal) {
			status = "failed"
		}
		result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: status, TradeNo: refund.OutTradeNo, ProviderTradeNo: refund.RefundId, Payload: mustJSON(refund), EventID: notify.Id}
		if refund.Amount != nil {
			result.Amount = int64(refund.Amount.Refund)
			result.Currency = strings.ToUpper(firstAny(c, "currency"))
		}
		return result, nil
	}
	return nil, fmt.Errorf("抖音支付不支持回调事件 %q", notify.EventType)
}

func validateDouyinMerchantIdentity(merchantID, appID string, c map[string]any, requireAppID bool) error {
	merchantID = strings.TrimSpace(merchantID)
	expectedMerchantID := strings.TrimSpace(text(c, "merchant_id"))
	if merchantID == "" || expectedMerchantID == "" || merchantID != expectedMerchantID {
		return errors.New("抖音支付响应商户号不匹配")
	}
	appID = strings.TrimSpace(appID)
	expectedAppID := strings.TrimSpace(text(c, "app_id"))
	if requireAppID && appID == "" {
		return errors.New("抖音支付响应缺少 appid")
	}
	if appID != "" && (expectedAppID == "" || appID != expectedAppID) {
		return errors.New("抖音支付响应 appid 不匹配")
	}
	return nil
}

func populateDouyinPaymentAmount(result *bizpayment.PaymentResult, amount *douyin.AmountInfo, raw []byte) error {
	if result == nil || amount == nil {
		return nil
	}
	result.Amount = int64(amount.Total)
	result.Currency = strings.ToUpper(strings.TrimSpace(amount.Currency))
	result.PayerCurrency = strings.ToUpper(strings.TrimSpace(amount.PayerCurrency))
	if result.PayerCurrency == "" {
		result.PayerCurrency = result.Currency
	}

	var amountObject map[string]any
	if object := jsonObject(raw); object != nil {
		amountObject, _ = valueAtPath(object, "amount").(map[string]any)
	}
	payerTotal, present, err := parseJSONInteger(amountObject, "payer_total")
	if err != nil {
		return fmt.Errorf("解析抖音支付 amount.payer_total: %w", err)
	}
	if !present && len(raw) == 0 && amount.PayerTotal > 0 {
		payerTotal = int64(amount.PayerTotal)
		present = true
	}
	if !present {
		return nil
	}
	if payerTotal < 0 || payerTotal > result.Amount {
		return errors.New("抖音支付用户实付金额无效")
	}
	result.PayerPaidAmount = payerTotal
	result.CashPaidAmount = payerTotal
	result.DiscountAmount = result.Amount - payerTotal
	result.AmountBreakdownKnown = true
	return nil
}

func douyinNotifyPlaintext(notify *douyin.NotifyReq, apiKey string) ([]byte, error) {
	if notify == nil || notify.Resource == nil {
		return nil, errors.New("抖音支付回调缺少 resource")
	}
	return douyin.DecryptNotifyCipherTextToBytes(
		notify.Resource.Ciphertext,
		notify.Resource.Nonce,
		notify.Resource.AssociatedData,
		apiKey,
	)
}
