package billing

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/billing/alipay"
)

const (
	billingCreditUnitAgentTeamCredit = "agentteam_credit"
	billingCurrencyCNY               = "CNY"
	billingCreditAmountPerCNY        = int64(100)
	billingCreditAtomsPerCredit      = int64(1_000_000)
	billingCreditAtomsPerCNY         = billingCreditAmountPerCNY * billingCreditAtomsPerCredit
	billingCustomMinCredits          = int64(100)
	billingOrderTTL                  = 30 * time.Minute

	billingProviderAlipay      = "alipay"
	billingProviderModePagePay = "page_pay"

	billingEventSourceNotify = "notify"
	billingEventSourceQuery  = "query"

	alipayTradeStatusWaitBuyerPay = "WAIT_BUYER_PAY"
	alipayTradeStatusClosed       = "TRADE_CLOSED"
	alipayTradeStatusSuccess      = "TRADE_SUCCESS"
	alipayTradeStatusFinished     = "TRADE_FINISHED"
	alipaySuccessCode             = "10000"
	alipaySubCodeTradeNotExist    = "ACQ.TRADE_NOT_EXIST"

	billingErrorDisabled      = "billing_disabled"
	billingErrorUnavailable   = "billing_unavailable"
	billingErrorForbidden     = "billing_forbidden"
	billingErrorInvalidInput  = "billing_invalid_input"
	billingErrorConflict      = "billing_conflict"
	billingErrorNotFound      = "billing_not_found"
	billingErrorProviderState = "billing_provider_state"
)

type Handler struct {
	enabled       bool
	organizations OrganizationProvider
	billingClient BillingClient
	seatBilling   SeatBillingStore
	alipay        AlipayClient
	publicBaseURL string
	returnBaseURL string
	now           func() time.Time
	logger        *slog.Logger
}

// Summary handles GET /api/v1/billing/summary.
// It returns the hosted credit summary for the organization selected by
// X-Workspace-Id after organization owner/admin authorization.
func (handler *Handler) Summary(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	summary, err := handler.billingClient.LoadSummary(r.Context(), org.ID)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentSummary(summary)), nil
}

// Ledger handles GET /api/v1/billing/ledger.
// It returns one bounded month/cursor page of hosted credit ledger rows.
func (handler *Handler) Ledger(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	options, err := billingListOptionsFromRequest(r.Gin)
	if err != nil {
		return api.Response{}, err
	}
	page, err := handler.billingClient.ListLedger(r.Context(), org.ID, options)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentLedgerPage(page)), nil
}

// ModelUsage handles GET /api/v1/billing/model-usage.
// It returns the selected month's hosted model spend grouped by model.
func (handler *Handler) ModelUsage(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	options, err := billingListOptionsFromRequest(r.Gin)
	if err != nil {
		return api.Response{}, err
	}
	usage, err := handler.billingClient.ListModelUsage(r.Context(), org.ID, options)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentModelUsage(usage)), nil
}

// Payments handles GET /api/v1/billing/payments.
// It returns one bounded month/cursor page of hosted credit payment orders.
func (handler *Handler) Payments(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	options, err := billingListOptionsFromRequest(r.Gin)
	if err != nil {
		return api.Response{}, err
	}
	page, err := handler.billingClient.ListPayments(r.Context(), org.ID, options)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentPaymentOrderPage(page)), nil
}

// Pricing handles GET /api/v1/billing/pricing.
// It returns current recharge constants and hosted model pricing display facts.
func (handler *Handler) Pricing(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	pricing, err := handler.billingClient.LoadPricing(r.Context(), org.ID)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentPricing(pricing)), nil
}

// CreateOrder handles POST /api/v1/billing/credit-recharge-orders.
// It creates or returns one idempotent hosted credit recharge order and signs
// an Alipay page-pay URL for the browser.
func (handler *Handler) CreateOrder(r api.AuthenticatedJSON[dto.BillingCreditRechargeOrderCreateRequest]) (api.Response, error) {
	org, err := handler.authorizedOrganization(r.AuthenticatedRequest)
	if err != nil {
		return api.Response{}, err
	}
	idempotencyKey := strings.TrimSpace(r.Gin.GetHeader("Idempotency-Key"))
	if idempotencyKey == "" {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "Idempotency-Key is required", billingErrorInvalidInput)
	}
	creditAmount := r.Body.CreditAmount
	if creditAmount < billingCustomMinCredits {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusUnprocessableEntity, "creditAmount is below the minimum", billingErrorInvalidInput)
	}

	now := handler.currentTime()
	order, err := handler.billingClient.CreateCreditRechargeOrder(r.Context(), createCreditRechargeOrderParams{
		OrganizationID:     org.ID,
		WorkspaceID:        billingWorkspaceID(r.Gin),
		CreatedByUserID:    r.UserID(),
		IdempotencyKey:     idempotencyKey,
		CreditUnit:         billingCreditUnitAgentTeamCredit,
		CreditAmount:       creditAmount,
		Currency:           billingCurrencyCNY,
		PayableAmountCents: creditAmount,
		PriceSnapshot: map[string]any{
			"source":               "hosted-credit-atoms-v1",
			"creditAmountPerCny":   billingCreditAmountPerCNY,
			"creditAtomsPerCredit": billingCreditAtomsPerCredit,
			"creditAtomsPerCny":    billingCreditAtomsPerCNY,
			"creditUnit":           billingCreditUnitAgentTeamCredit,
			"currency":             billingCurrencyCNY,
		},
		Provider:     billingProviderAlipay,
		ProviderMode: billingProviderModePagePay,
		ExpiresAt:    now.Add(billingOrderTTL),
	})
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	paymentURL, err := handler.createPagePayURL(order)
	if err != nil {
		return api.Response{}, dependencyUnavailable(err)
	}
	return api.Created(presentCreditRechargeOrder(order, paymentURL)), nil
}

// GetOrder handles GET /api/v1/billing/credit-recharge-orders/:orderId.
// It returns one authorized organization-scoped recharge order without querying
// Alipay.
func (handler *Handler) GetOrder(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	orderID := strings.TrimSpace(r.Gin.Param("orderId"))
	if orderID == "" {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "orderId is required", billingErrorInvalidInput)
	}
	order, err := handler.billingClient.LoadCreditRechargeOrder(r.Context(), org.ID, orderID)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentCreditRechargeOrder(order, "")), nil
}

// SyncOrder handles POST /api/v1/billing/credit-recharge-orders/:orderId/sync.
// It queries Alipay for one pending order and forwards paid/closed provider
// state to agentteam-maas.
func (handler *Handler) SyncOrder(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	orderID := strings.TrimSpace(r.Gin.Param("orderId"))
	if orderID == "" {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "orderId is required", billingErrorInvalidInput)
	}
	order, err := handler.billingClient.LoadCreditRechargeOrder(r.Context(), org.ID, orderID)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	if order.Status != "pending" {
		return api.OK(presentCreditRechargeOrder(order, "")), nil
	}
	if handler.alipay == nil {
		return api.Response{}, dependencyUnavailable(fmt.Errorf("Alipay client is not configured"))
	}
	result, err := handler.alipay.QueryTrade(r.Context(), order.OutTradeNo)
	if err != nil {
		return api.Response{}, dependencyUnavailable(err)
	}
	if strings.TrimSpace(result.Code) != alipaySuccessCode {
		if alipayQueryTradeStillPending(result) {
			return api.OK(presentCreditRechargeOrder(order, "")), nil
		}
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusServiceUnavailable, "Alipay query is unavailable", billingErrorProviderState)
	}
	if strings.TrimSpace(result.OutTradeNo) != order.OutTradeNo {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusConflict, "Alipay order does not match", billingErrorConflict)
	}

	settled, err := handler.applyProviderStatus(r.Context(), org.ID, order, providerStatusInput{
		Source:              billingEventSourceQuery,
		ProviderTradeNo:     result.TradeNo,
		ProviderTradeStatus: result.TradeStatus,
		Amount:              result.TotalAmount,
		RawPayloadSHA256:    firstPresentString(result.RawPayloadSHA256, sha256Hex([]byte(result.OutTradeNo+"|"+result.TradeStatus+"|"+result.TotalAmount))),
		OccurredAt:          firstNonZeroTime(parseAlipayFormTime(result.SendPayDate), handler.currentTime()),
	})
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(presentCreditRechargeOrder(settled, "")), nil
}

func alipayQueryTradeStillPending(result alipay.QueryResult) bool {
	return strings.TrimSpace(result.SubCode) == alipaySubCodeTradeNotExist
}

// AlipayNotify handles POST /api/v1/billing/alipay/notify.
// It verifies an Alipay form notification and settles the provider state
// through agentteam-maas before returning Alipay's plain text acknowledgment.
func (handler *Handler) AlipayNotify(ctx *gin.Context) {
	if err := handler.handleAlipayNotify(ctx); err != nil {
		if handler != nil && handler.logger != nil {
			handler.logger.WarnContext(ctx.Request.Context(), "billing Alipay notify failed", slog.Any("error", err))
		}
		ctx.String(http.StatusOK, "fail")
		return
	}
	ctx.String(http.StatusOK, "success")
}

// AlipayReturn handles GET /api/v1/billing/alipay/return.
// It redirects the browser back to the workspace billing screen without
// settling payment state.
func (handler *Handler) AlipayReturn(ctx *gin.Context) {
	workspaceID := strings.TrimSpace(ctx.Query("workspace_id"))
	if workspaceID == "" {
		ctx.Redirect(http.StatusFound, "/workspaces")
		return
	}
	params := url.Values{}
	if orderID := strings.TrimSpace(ctx.Query("order_id")); orderID != "" {
		params.Set("payment_order_id", orderID)
	}
	target, err := billingReturnTarget(handler.returnBaseURL, workspaceID, params)
	if err != nil {
		if handler != nil && handler.logger != nil {
			handler.logger.WarnContext(ctx.Request.Context(), "billing Alipay return target failed", slog.Any("error", err))
		}
		target = billingReturnTargetPath(workspaceID, params)
	}
	ctx.Redirect(http.StatusFound, target)
}

func (handler *Handler) createPagePayURL(order billingPaymentOrder) (string, error) {
	if handler.alipay == nil {
		return "", fmt.Errorf("Alipay client is not configured")
	}
	notifyURL, err := handler.publicURL("/api/v1/billing/alipay/notify", nil)
	if err != nil {
		return "", err
	}
	returnURL, err := handler.publicURL("/api/v1/billing/alipay/return", url.Values{
		"workspace_id": []string{order.WorkspaceID},
		"order_id":     []string{order.OrderID},
	})
	if err != nil {
		return "", err
	}
	return handler.alipay.CreatePagePayURL(alipay.PagePayInput{
		OutTradeNo: order.OutTradeNo,
		Subject:    "AgentTeam hosted model credits",
		Amount:     formatCNYAmount(order.PayableAmountCents),
		Body:       fmt.Sprintf("%d AgentTeam hosted model credits", order.CreditAmount),
		ReturnURL:  returnURL,
		NotifyURL:  notifyURL,
	})
}

func (handler *Handler) publicURL(path string, query url.Values) (string, error) {
	base, err := url.Parse(strings.TrimSpace(handler.publicBaseURL))
	if err != nil {
		return "", fmt.Errorf("parse AGENTTEAM_PUBLIC_BASE_URL: %w", err)
	}
	if base.Scheme == "" || base.Host == "" {
		return "", fmt.Errorf("AGENTTEAM_PUBLIC_BASE_URL must be an absolute URL")
	}
	base.Path = strings.TrimRight(base.Path, "/") + "/" + strings.TrimLeft(path, "/")
	base.RawQuery = query.Encode()
	return base.String(), nil
}

func billingReturnTarget(returnBaseURL string, workspaceID string, query url.Values) (string, error) {
	return billingReturnTargetWithFragment(returnBaseURL, workspaceID, query, "payments")
}

func billingReturnTargetWithFragment(returnBaseURL string, workspaceID string, query url.Values, fragment string) (string, error) {
	if strings.TrimSpace(returnBaseURL) == "" {
		return billingReturnTargetPathWithFragment(workspaceID, query, fragment), nil
	}
	base, err := url.Parse(strings.TrimSpace(returnBaseURL))
	if err != nil {
		return "", fmt.Errorf("parse AGENTTEAM_BILLING_RETURN_BASE_URL: %w", err)
	}
	if base.Scheme == "" || base.Host == "" {
		return "", fmt.Errorf("AGENTTEAM_BILLING_RETURN_BASE_URL must be an absolute URL")
	}
	base.Path = strings.TrimRight(base.Path, "/") + "/w/" + url.PathEscape(workspaceID) + "/org/billing"
	base.RawQuery = query.Encode()
	base.Fragment = strings.TrimPrefix(strings.TrimSpace(fragment), "#")
	return base.String(), nil
}

func billingReturnTargetPath(workspaceID string, query url.Values) string {
	return billingReturnTargetPathWithFragment(workspaceID, query, "payments")
}

func billingReturnTargetPathWithFragment(workspaceID string, query url.Values, fragment string) string {
	target := "/w/" + url.PathEscape(workspaceID) + "/org/billing"
	if encoded := query.Encode(); encoded != "" {
		target += "?" + encoded
	}
	if normalized := strings.TrimPrefix(strings.TrimSpace(fragment), "#"); normalized != "" {
		target += "#" + normalized
	}
	return target
}

type providerStatusInput struct {
	Source              string
	ProviderTradeNo     string
	ProviderTradeStatus string
	Amount              string
	RawPayloadSHA256    string
	OccurredAt          time.Time
}

func (handler *Handler) applyProviderStatus(ctx context.Context, organizationID string, order billingPaymentOrder, input providerStatusInput) (billingPaymentOrder, error) {
	switch strings.TrimSpace(input.ProviderTradeStatus) {
	case alipayTradeStatusWaitBuyerPay, "":
		return order, nil
	case alipayTradeStatusSuccess, alipayTradeStatusFinished:
		amountCents, err := parseCNYAmountCents(input.Amount)
		if err != nil {
			return billingPaymentOrder{}, api.NewHTTPErrorWithCodeAndCause(http.StatusConflict, "Alipay amount is invalid", billingErrorConflict, err)
		}
		if amountCents != order.PayableAmountCents {
			return billingPaymentOrder{}, api.NewHTTPErrorWithCode(http.StatusConflict, "Alipay amount does not match", billingErrorConflict)
		}
		settled, err := handler.billingClient.ConfirmCreditRechargeOrderPaid(ctx, order.OrderID, confirmCreditRechargeOrderPaidParams{
			OrganizationID:      organizationID,
			Provider:            billingProviderAlipay,
			Source:              input.Source,
			OutTradeNo:          order.OutTradeNo,
			ProviderTradeNo:     input.ProviderTradeNo,
			ProviderTradeStatus: input.ProviderTradeStatus,
			PaidAmountCents:     amountCents,
			Currency:            billingCurrencyCNY,
			PaidAt:              input.OccurredAt,
			RawPayloadSHA256:    input.RawPayloadSHA256,
		})
		if err != nil {
			return billingPaymentOrder{}, mapBillingDependencyError(err)
		}
		return settled, nil
	case alipayTradeStatusClosed:
		amountCents := order.PayableAmountCents
		if strings.TrimSpace(input.Amount) != "" {
			parsed, err := parseCNYAmountCents(input.Amount)
			if err != nil {
				return billingPaymentOrder{}, api.NewHTTPErrorWithCodeAndCause(http.StatusConflict, "Alipay amount is invalid", billingErrorConflict, err)
			}
			amountCents = parsed
		}
		if amountCents != order.PayableAmountCents {
			return billingPaymentOrder{}, api.NewHTTPErrorWithCode(http.StatusConflict, "Alipay amount does not match", billingErrorConflict)
		}
		closed, err := handler.billingClient.CloseCreditRechargeOrder(ctx, order.OrderID, closeCreditRechargeOrderParams{
			OrganizationID:      organizationID,
			Provider:            billingProviderAlipay,
			Source:              input.Source,
			OutTradeNo:          order.OutTradeNo,
			ProviderTradeNo:     input.ProviderTradeNo,
			ProviderTradeStatus: input.ProviderTradeStatus,
			AmountCents:         amountCents,
			Currency:            billingCurrencyCNY,
			ClosedAt:            input.OccurredAt,
			RawPayloadSHA256:    input.RawPayloadSHA256,
		})
		if err != nil {
			return billingPaymentOrder{}, mapBillingDependencyError(err)
		}
		return closed, nil
	default:
		return billingPaymentOrder{}, api.NewHTTPErrorWithCode(http.StatusConflict, "Alipay trade status is not settleable", billingErrorProviderState)
	}
}

func (handler *Handler) handleAlipayNotify(ctx *gin.Context) error {
	if handler == nil || !handler.enabled {
		return fmt.Errorf("billing is disabled")
	}
	if handler.alipay == nil || handler.billingClient == nil {
		return fmt.Errorf("billing dependency is not configured")
	}
	raw, err := io.ReadAll(io.LimitReader(ctx.Request.Body, maxBillingResponseBodyBytes))
	if err != nil {
		return fmt.Errorf("read Alipay notify body: %w", err)
	}
	form, err := url.ParseQuery(string(raw))
	if err != nil {
		return fmt.Errorf("parse Alipay notify form: %w", err)
	}
	if err := handler.alipay.VerifyNotify(form); err != nil {
		return err
	}
	outTradeNo := strings.TrimSpace(form.Get("out_trade_no"))
	if outTradeNo == "" {
		return fmt.Errorf("Alipay notify out_trade_no is required")
	}
	tradeStatus := strings.TrimSpace(form.Get("trade_status"))
	switch tradeStatus {
	case alipayTradeStatusSuccess, alipayTradeStatusFinished, alipayTradeStatusClosed:
	default:
		return fmt.Errorf("Alipay notify trade status is not settleable")
	}
	order, err := handler.billingClient.LoadCreditRechargeOrderByOutTradeNo(ctx.Request.Context(), outTradeNo)
	if err != nil {
		return err
	}
	occurredAt := firstNonZeroTime(
		parseAlipayFormTime(form.Get("gmt_payment")),
		parseAlipayFormTime(form.Get("gmt_close")),
		parseAlipayFormTime(form.Get("notify_time")),
		handler.currentTime(),
	)
	_, err = handler.applyProviderStatus(ctx.Request.Context(), order.OrganizationID, order, providerStatusInput{
		Source:              billingEventSourceNotify,
		ProviderTradeNo:     strings.TrimSpace(form.Get("trade_no")),
		ProviderTradeStatus: tradeStatus,
		Amount:              strings.TrimSpace(form.Get("total_amount")),
		RawPayloadSHA256:    sha256Hex(raw),
		OccurredAt:          occurredAt,
	})
	return err
}

func billingListOptionsFromRequest(ctx *gin.Context) (billingListOptions, error) {
	limit := 0
	if rawLimit := strings.TrimSpace(ctx.Query("limit")); rawLimit != "" {
		parsed, err := strconv.Atoi(rawLimit)
		if err != nil || parsed < 0 {
			return billingListOptions{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "limit is invalid", billingErrorInvalidInput)
		}
		limit = parsed
	}
	return billingListOptions{
		Month:  strings.TrimSpace(ctx.Query("month")),
		Limit:  limit,
		Cursor: strings.TrimSpace(ctx.Query("cursor")),
		Type:   strings.TrimSpace(ctx.Query("type")),
	}, nil
}

func presentSummary(summary billingSummary) dto.BillingSummaryResponse {
	return dto.BillingSummaryResponse(summary)
}

func presentLedgerPage(page billingLedgerPage) dto.BillingLedgerPageResponse {
	items := make([]dto.BillingLedgerItemResponse, 0, len(page.Items))
	for _, item := range page.Items {
		items = append(items, dto.BillingLedgerItemResponse(item))
	}
	return dto.BillingLedgerPageResponse{Items: items, NextCursor: page.NextCursor}
}

func presentModelUsage(usage billingModelUsage) dto.BillingModelUsageResponse {
	items := make([]dto.BillingModelUsageItemResponse, 0, len(usage.Items))
	for _, item := range usage.Items {
		items = append(items, dto.BillingModelUsageItemResponse(item))
	}
	return dto.BillingModelUsageResponse{
		Month:                    usage.Month,
		CreditUnit:               usage.CreditUnit,
		TotalRequestCount:        usage.TotalRequestCount,
		TotalConsumedCreditAtoms: usage.TotalConsumedCreditAtoms,
		Items:                    items,
	}
}

func presentPaymentOrderPage(page billingPaymentOrderPage) dto.BillingPaymentOrderPageResponse {
	items := make([]dto.BillingPaymentOrderResponse, 0, len(page.Items))
	for _, order := range page.Items {
		items = append(items, presentPaymentOrder(order))
	}
	return dto.BillingPaymentOrderPageResponse{Items: items, NextCursor: page.NextCursor}
}

func presentPricing(pricing billingPricing) dto.BillingPricingResponse {
	models := make([]dto.BillingHostedModelPricingResponse, 0, len(pricing.HostedModels))
	for _, model := range pricing.HostedModels {
		models = append(models, presentHostedModelPricing(model))
	}
	return dto.BillingPricingResponse{
		CreditUnit:           pricing.CreditUnit,
		Currency:             pricing.Currency,
		CreditAmountPerCNY:   pricing.CreditAmountPerCNY,
		CreditAtomsPerCredit: pricing.CreditAtomsPerCredit,
		CreditAtomsPerCNY:    pricing.CreditAtomsPerCNY,
		RechargePresets:      pricing.RechargePresets,
		CustomMinCredits:     pricing.CustomMinCredits,
		HostedModels:         models,
	}
}

func presentHostedModelPricing(model billingHostedModelPricing) dto.BillingHostedModelPricingResponse {
	return dto.BillingHostedModelPricingResponse{
		ID:                               model.ID,
		ModelReference:                   model.ModelReference,
		HostedModelID:                    model.HostedModelID,
		DisplayName:                      model.DisplayName,
		PricingVersionID:                 model.PricingVersionID,
		EffectivePricingVersionID:        model.EffectivePricingVersionID,
		PricingFormulaVersion:            model.PricingFormulaVersion,
		BillableDimensions:               append([]string(nil), model.BillableDimensions...),
		ContextTiers:                     presentHostedPricingTiers(model.ContextTiers),
		InputCreditAtomsPerMillionTokens: model.InputCreditAtomsPerMillionTokens,
		CacheReadInputCreditAtomsPerMillionTokens:     model.CacheReadInputCreditAtomsPerMillionTokens,
		OutputCreditAtomsPerMillionTokens:             model.OutputCreditAtomsPerMillionTokens,
		BasePricingVersionID:                          model.BasePricingVersionID,
		BasePricingFormulaVersion:                     model.BasePricingFormulaVersion,
		BaseBillableDimensions:                        append([]string(nil), model.BaseBillableDimensions...),
		BaseContextTiers:                              presentHostedPricingTiers(model.BaseContextTiers),
		BaseInputCreditAtomsPerMillionTokens:          model.BaseInputCreditAtomsPerMillionTokens,
		BaseCacheReadInputCreditAtomsPerMillionTokens: model.BaseCacheReadInputCreditAtomsPerMillionTokens,
		BaseOutputCreditAtomsPerMillionTokens:         model.BaseOutputCreditAtomsPerMillionTokens,
		PricingOverride:                               presentHostedPricingOverride(model.PricingOverride),
	}
}

func presentHostedPricingOverride(override *HostedPricingOverride) *dto.HostedPricingOverrideResponse {
	if override == nil {
		return nil
	}
	return &dto.HostedPricingOverrideResponse{
		ID:                        override.ID,
		HostedModelID:             override.HostedModelID,
		ScopeType:                 override.ScopeType,
		ScopeID:                   override.ScopeID,
		BasePricingVersionID:      override.BasePricingVersionID,
		EffectivePricingVersionID: override.EffectivePricingVersionID,
		OverrideType:              override.OverrideType,
		DisplayLabel:              override.DisplayLabel,
		Priority:                  override.Priority,
		EffectiveFrom:             override.EffectiveFrom,
		EffectiveTo:               override.EffectiveTo,
		Status:                    override.Status,
	}
}

func presentHostedPricingTiers(tiers []HostedPricingTier) []dto.HostedModelPricingTierResponse {
	if len(tiers) == 0 {
		return nil
	}
	presented := make([]dto.HostedModelPricingTierResponse, 0, len(tiers))
	for _, tier := range tiers {
		presented = append(presented, dto.HostedModelPricingTierResponse{
			ID:               tier.ID,
			MinContextTokens: tier.MinContextTokens,
			MaxContextTokens: tier.MaxContextTokens,
			Rates: dto.HostedModelPricingTierRatesResponse{
				InputTokensCreditAtomsPerMillionTokens:    tier.Rates.InputTokensCreditAtomsPerMillionTokens,
				CacheReadInputCreditAtomsPerMillionTokens: tier.Rates.CacheReadInputCreditAtomsPerMillionTokens,
				OutputCreditAtomsPerMillionTokens:         tier.Rates.OutputCreditAtomsPerMillionTokens,
			},
		})
	}
	return presented
}

func presentPaymentOrder(order billingPaymentOrder) dto.BillingPaymentOrderResponse {
	return dto.BillingPaymentOrderResponse{
		ID:                 order.OrderID,
		OutTradeNo:         order.OutTradeNo,
		Provider:           order.Provider,
		Status:             order.Status,
		CreditUnit:         order.CreditUnit,
		CreditAmount:       order.CreditAmount,
		Currency:           order.Currency,
		PayableAmountCents: order.PayableAmountCents,
		ExpiresAt:          order.ExpiresAt,
		CreatedAt:          order.CreatedAt,
		PaidAt:             order.PaidAt,
		ClosedAt:           order.ClosedAt,
	}
}

func presentCreditRechargeOrder(order billingPaymentOrder, paymentURL string) dto.BillingCreditRechargeOrderResponse {
	return dto.BillingCreditRechargeOrderResponse{
		OrderID:            order.OrderID,
		OutTradeNo:         order.OutTradeNo,
		Status:             order.Status,
		CreditUnit:         order.CreditUnit,
		CreditAmount:       order.CreditAmount,
		Currency:           order.Currency,
		PayableAmountCents: order.PayableAmountCents,
		PaymentURL:         strings.TrimSpace(paymentURL),
		ExpiresAt:          order.ExpiresAt,
		PaidAt:             order.PaidAt,
		ClosedAt:           order.ClosedAt,
	}
}

func billingWorkspaceID(ctx *gin.Context) string {
	if ctx == nil {
		return ""
	}
	return strings.TrimSpace(ctx.GetHeader(api.HeaderWorkspaceID))
}

func (handler *Handler) currentTime() time.Time {
	if handler != nil && handler.now != nil {
		return handler.now().UTC()
	}
	return time.Now().UTC()
}

func formatCNYAmount(cents int64) string {
	if cents < 0 {
		cents = 0
	}
	return fmt.Sprintf("%d.%02d", cents/100, cents%100)
}

func parseCNYAmountCents(raw string) (int64, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return 0, fmt.Errorf("amount is required")
	}
	parts := strings.Split(trimmed, ".")
	if len(parts) > 2 || parts[0] == "" {
		return 0, fmt.Errorf("amount is invalid")
	}
	yuan, err := strconv.ParseInt(parts[0], 10, 64)
	if err != nil || yuan < 0 {
		return 0, fmt.Errorf("amount is invalid")
	}
	var fen int64
	if len(parts) == 2 {
		fraction := parts[1]
		if len(fraction) > 2 {
			return 0, fmt.Errorf("amount precision is invalid")
		}
		for len(fraction) < 2 {
			fraction += "0"
		}
		fen, err = strconv.ParseInt(fraction, 10, 64)
		if err != nil {
			return 0, fmt.Errorf("amount is invalid")
		}
	}
	return yuan*100 + fen, nil
}

func parseAlipayFormTime(raw string) time.Time {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return time.Time{}
	}
	location, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		location = time.FixedZone("CST", 8*60*60)
	}
	parsed, err := time.ParseInLocation("2006-01-02 15:04:05", trimmed, location)
	if err != nil {
		return time.Time{}
	}
	return parsed.UTC()
}

func firstNonZeroTime(values ...time.Time) time.Time {
	for _, value := range values {
		if !value.IsZero() {
			return value.UTC()
		}
	}
	return time.Now().UTC()
}

func sha256Hex(raw []byte) string {
	sum := sha256.Sum256(raw)
	return hex.EncodeToString(sum[:])
}

func firstPresentString(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}
