package billing

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"sort"
	"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 (
	billingProductAll          = "all"
	billingProductHostedCredit = "hosted_credit"
	billingProductSeat         = "seat"

	seatPaymentTimeoutExpress = "30m"
	seatPaymentSubject        = "AgentTeam organization seats"
)

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

// SeatSummary handles GET /api/v1/billing/seat-summary.
// It returns seat entitlement, current human count, current agent count, and
// over-limit flags for the current workspace organization.
func (handler *Handler) SeatSummary(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	seatStore, err := handler.requireSeatBilling()
	if err != nil {
		return api.Response{}, err
	}
	summary, err := seatStore.LoadSeatSummary(r.Context(), org.ID, handler.currentTime())
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentSeatSummary(summary)), nil
}

// SeatPlans handles GET /api/v1/billing/seat-plans.
// It returns the active self-service seat plan rows after organization billing
// authorization.
func (handler *Handler) SeatPlans(r api.AuthenticatedRequest) (api.Response, error) {
	if _, err := handler.authorizedOrganization(r); err != nil {
		return api.Response{}, err
	}
	seatStore, err := handler.requireSeatBilling()
	if err != nil {
		return api.Response{}, err
	}
	plans, err := seatStore.ListSeatPlans(r.Context())
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentSeatPlans(plans)), nil
}

// SeatSubscription handles GET /api/v1/billing/seat-subscription.
// It returns the current or most recent paid seat subscription row for the
// current workspace organization when one exists.
func (handler *Handler) SeatSubscription(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	seatStore, err := handler.requireSeatBilling()
	if err != nil {
		return api.Response{}, err
	}
	subscription, found, err := seatStore.LoadSeatSubscription(r.Context(), org.ID)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentSeatSubscriptionEnvelope(subscription, found)), nil
}

// ListSeatOrders handles GET /api/v1/billing/seat-orders.
// It returns a bounded organization-scoped page of seat orders, optionally
// filtered by repeated or comma-separated status values.
func (handler *Handler) ListSeatOrders(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	seatStore, err := handler.requireSeatBilling()
	if err != nil {
		return api.Response{}, err
	}
	options, err := seatOrderListOptionsFromRequest(r.Gin, org.ID)
	if err != nil {
		return api.Response{}, err
	}
	orders, err := seatStore.ListSeatOrders(r.Context(), options)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentSeatOrderList(orders, "")), nil
}

// CreateSeatOrder handles POST /api/v1/billing/seat-orders.
// It creates or returns one idempotent pending seat order and signs an Alipay
// page-pay URL for the browser.
func (handler *Handler) CreateSeatOrder(r api.AuthenticatedJSON[dto.BillingSeatOrderCreateRequest]) (api.Response, error) {
	org, err := handler.authorizedOrganization(r.AuthenticatedRequest)
	if err != nil {
		return api.Response{}, err
	}
	seatStore, err := handler.requireSeatBilling()
	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)
	}
	order, err := seatStore.CreateSeatOrder(r.Context(), seatOrderCreateParams{
		OrganizationID:    org.ID,
		CreatedByUserID:   r.UserID(),
		OrderType:         r.Body.OrderType,
		ToPlanKey:         r.Body.ToPlanKey,
		BillingPeriod:     r.Body.BillingPeriod,
		SeatQuantityAfter: r.Body.SeatQuantityAfter,
		IdempotencyKey:    idempotencyKey,
		Now:               handler.currentTime(),
	})
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	paymentURL := ""
	if order.Status == "pending" {
		paymentURL, err = handler.createSeatPagePayURL(order, billingWorkspaceID(r.Gin))
		if err != nil {
			return api.Response{}, dependencyUnavailable(err)
		}
	}
	return api.Created(presentSeatOrder(order, paymentURL)), nil
}

// GetSeatOrder handles GET /api/v1/billing/seat-orders/:orderId.
// It returns one organization-scoped seat order without querying Alipay.
func (handler *Handler) GetSeatOrder(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	seatStore, err := handler.requireSeatBilling()
	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, found, err := seatStore.LoadSeatOrder(r.Context(), org.ID, orderID)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	if !found {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusNotFound, "Billing resource not found", billingErrorNotFound)
	}
	paymentURL := ""
	if order.Status == "pending" {
		if paymentURL, err = handler.createSeatPagePayURL(order, billingWorkspaceID(r.Gin)); err != nil {
			return api.Response{}, dependencyUnavailable(err)
		}
	}
	return api.OK(presentSeatOrder(order, paymentURL)), nil
}

// SyncSeatOrder handles POST /api/v1/billing/seat-orders/:orderId/sync.
// It queries Alipay for one pending seat order and applies paid provider state
// through the AgentTeam-owned seat billing store.
func (handler *Handler) SyncSeatOrder(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	seatStore, err := handler.requireSeatBilling()
	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, found, err := seatStore.LoadSeatOrder(r.Context(), org.ID, orderID)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	if !found {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusNotFound, "Billing resource not found", billingErrorNotFound)
	}
	if order.Status != "pending" && order.Status != "paid_unapplied" {
		return api.OK(presentSeatOrder(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.ProviderOrderID)
	if err != nil {
		return api.Response{}, dependencyUnavailable(err)
	}
	if strings.TrimSpace(result.Code) != alipaySuccessCode {
		if order.Status == "pending" && alipayQueryTradeStillPending(result) {
			return api.OK(presentSeatOrder(order, "")), nil
		}
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusServiceUnavailable, "Alipay query is unavailable", billingErrorProviderState)
	}
	if strings.TrimSpace(result.OutTradeNo) != order.ProviderOrderID {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusConflict, "Alipay order does not match", billingErrorConflict)
	}

	settled, err := handler.applySeatProviderStatus(r.Context(), order, seatProviderStatusInput{
		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(presentSeatOrder(settled, "")), nil
}

// CancelSeatOrder handles POST /api/v1/billing/seat-orders/:orderId/cancel.
// It closes the Alipay trade before marking the local pending seat order
// cancelled.
func (handler *Handler) CancelSeatOrder(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	seatStore, err := handler.requireSeatBilling()
	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, found, err := seatStore.LoadSeatOrder(r.Context(), org.ID, orderID)
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	if !found {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusNotFound, "Billing resource not found", billingErrorNotFound)
	}
	if order.Status != "pending" {
		return api.OK(presentSeatOrder(order, "")), nil
	}
	if handler.alipay == nil {
		return api.Response{}, dependencyUnavailable(fmt.Errorf("Alipay client is not configured"))
	}
	closed, err := handler.alipay.CloseTrade(r.Context(), order.ProviderOrderID)
	if err != nil {
		return api.Response{}, dependencyUnavailable(err)
	}
	if strings.TrimSpace(closed.Code) != alipaySuccessCode {
		reconciled, handled, err := handler.reconcileSeatCancelAfterCloseFailure(r.Context(), order, r.UserID())
		if err != nil {
			return api.Response{}, err
		}
		if handled {
			return api.OK(presentSeatOrder(reconciled, "")), nil
		}
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusServiceUnavailable, "Alipay close is unavailable", billingErrorProviderState)
	}
	if strings.TrimSpace(closed.OutTradeNo) != order.ProviderOrderID {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusConflict, "Alipay order does not match", billingErrorConflict)
	}
	now := handler.currentTime()
	cancelled, err := handler.cancelSeatOrderLocally(r.Context(), seatStore, order, r.UserID(), now, map[string]string{
		"source":                "cancel",
		"provider_trade_no":     closed.TradeNo,
		"provider_trade_status": alipayTradeStatusClosed,
		"raw_payload_sha256":    sha256Hex([]byte(order.ProviderOrderID + "|closed")),
		"occurred_at":           now.Format(time.RFC3339),
	})
	if err != nil {
		return api.Response{}, mapBillingDependencyError(err)
	}
	return api.OK(presentSeatOrder(cancelled, "")), nil
}

// History handles GET /api/v1/billing/history.
// It returns a bounded month page of hosted credit, seat billing, or combined
// payment history for the current workspace organization.
func (handler *Handler) History(r api.AuthenticatedRequest) (api.Response, error) {
	org, err := handler.authorizedOrganization(r)
	if err != nil {
		return api.Response{}, err
	}
	options, err := billingHistoryOptionsFromRequest(r.Gin, handler.currentTime())
	if err != nil {
		return api.Response{}, err
	}

	items := make([]dto.BillingHistoryItemResponse, 0, options.limit)
	if options.product == billingProductAll || options.product == billingProductHostedCredit {
		if handler.billingClient == nil {
			return api.Response{}, dependencyUnavailable(fmt.Errorf("hosted billing client is not configured"))
		}
		page, err := handler.billingClient.ListPayments(r.Context(), org.ID, billingListOptions{Month: options.month, Limit: options.limit})
		if err != nil {
			return api.Response{}, mapBillingDependencyError(err)
		}
		for _, order := range page.Items {
			items = append(items, presentHostedCreditHistoryItem(order))
		}
	}
	if options.product == billingProductAll || options.product == billingProductSeat {
		seatStore, err := handler.requireSeatBilling()
		if err != nil {
			return api.Response{}, err
		}
		rows, err := seatStore.ListSeatPaymentHistory(r.Context(), seatPaymentHistoryOptions{
			OrganizationID: org.ID,
			MonthStart:     options.monthStart,
			MonthEnd:       options.monthEnd,
			Limit:          options.limit,
		})
		if err != nil {
			return api.Response{}, mapBillingDependencyError(err)
		}
		for _, row := range rows {
			items = append(items, presentSeatHistoryItem(row))
		}
	}
	sort.SliceStable(items, func(left, right int) bool {
		return billingHistorySortTime(items[left]).After(billingHistorySortTime(items[right]))
	})
	if len(items) > options.limit {
		items = items[:options.limit]
	}
	return api.OK(dto.BillingHistoryResponse{Items: items}), nil
}

// SeatAlipayNotify handles POST /api/v1/billing/seat-orders/alipay/notify.
// It verifies an Alipay form notification and settles paid seat order state
// through the AgentTeam-owned seat billing store.
func (handler *Handler) SeatAlipayNotify(ctx *gin.Context) {
	if err := handler.handleSeatAlipayNotify(ctx); err != nil {
		if handler != nil && handler.logger != nil {
			handler.logger.WarnContext(ctx.Request.Context(), "seat billing Alipay notify failed", "error", err)
		}
		ctx.String(http.StatusOK, "fail")
		return
	}
	ctx.String(http.StatusOK, "success")
}

// SeatAlipayReturn handles GET /api/v1/billing/seat-orders/alipay/return.
// It redirects the browser back to Paid Center seat billing without treating
// the return request as settlement evidence.
func (handler *Handler) SeatAlipayReturn(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("seat_order_id", orderID)
	}
	target, err := billingReturnTargetWithFragment(handler.returnBaseURL, workspaceID, params, "seats")
	if err != nil {
		target = billingReturnTargetPathWithFragment(workspaceID, params, "seats")
	}
	ctx.Redirect(http.StatusFound, target)
}

func (handler *Handler) requireSeatBilling() (SeatBillingStore, error) {
	if handler == nil || handler.seatBilling == nil {
		return nil, dependencyUnavailable(fmt.Errorf("seat billing store is not configured"))
	}
	return handler.seatBilling, nil
}

func (handler *Handler) createSeatPagePayURL(order seatOrderView, workspaceID string) (string, error) {
	if handler.alipay == nil {
		return "", fmt.Errorf("Alipay client is not configured")
	}
	notifyURL, err := handler.publicURL("/api/v1/billing/seat-orders/alipay/notify", nil)
	if err != nil {
		return "", err
	}
	returnURL, err := handler.publicURL("/api/v1/billing/seat-orders/alipay/return", url.Values{
		"workspace_id": []string{workspaceID},
		"order_id":     []string{order.ID},
	})
	if err != nil {
		return "", err
	}
	return handler.alipay.CreatePagePayURL(alipay.PagePayInput{
		OutTradeNo:     order.ProviderOrderID,
		Subject:        seatPaymentSubject,
		Amount:         formatCNYAmount(order.AmountCents),
		Body:           fmt.Sprintf("%s %s x %d", order.OrderType, order.BillingPeriod, order.SeatQuantityAfter),
		TimeoutExpress: seatPaymentTimeoutExpress,
		ReturnURL:      returnURL,
		NotifyURL:      notifyURL,
	})
}

func (handler *Handler) applySeatProviderStatus(ctx context.Context, order seatOrderView, input seatProviderStatusInput) (seatOrderView, error) {
	switch strings.TrimSpace(input.ProviderTradeStatus) {
	case alipayTradeStatusWaitBuyerPay, alipayTradeStatusClosed, "":
		return order, nil
	case alipayTradeStatusSuccess, alipayTradeStatusFinished:
		amountCents, err := parseCNYAmountCents(input.Amount)
		if err != nil {
			return seatOrderView{}, api.NewHTTPErrorWithCodeAndCause(http.StatusConflict, "Alipay amount is invalid", billingErrorConflict, err)
		}
		seatStore, err := handler.requireSeatBilling()
		if err != nil {
			return seatOrderView{}, err
		}
		paidAt := input.OccurredAt.UTC()
		settled, err := seatStore.SettleSeatPaymentPaid(ctx, seatPaymentSettleParams{
			Provider:        billingProviderAlipay,
			ProviderOrderID: order.ProviderOrderID,
			AmountCents:     amountCents,
			Currency:        billingCurrencyCNY,
			PaidAt:          &paidAt,
			ConfirmedAt:     handler.currentTime(),
			RawPayloadJSON: seatPaymentEvidenceJSON(map[string]string{
				"source":                input.Source,
				"provider_trade_no":     input.ProviderTradeNo,
				"provider_trade_status": input.ProviderTradeStatus,
				"raw_payload_sha256":    input.RawPayloadSHA256,
				"occurred_at":           paidAt.Format(time.RFC3339),
			}),
		})
		if err != nil {
			return seatOrderView{}, mapBillingDependencyError(err)
		}
		return settled, nil
	default:
		return seatOrderView{}, api.NewHTTPErrorWithCode(http.StatusConflict, "Alipay trade status is not settleable", billingErrorProviderState)
	}
}

func (handler *Handler) reconcileSeatCancelAfterCloseFailure(ctx context.Context, order seatOrderView, cancelledByUserID string) (seatOrderView, bool, error) {
	result, err := handler.alipay.QueryTrade(ctx, order.ProviderOrderID)
	if err != nil {
		return seatOrderView{}, false, dependencyUnavailable(err)
	}
	if strings.TrimSpace(result.Code) != alipaySuccessCode {
		return seatOrderView{}, false, nil
	}
	if strings.TrimSpace(result.OutTradeNo) != order.ProviderOrderID {
		return seatOrderView{}, false, api.NewHTTPErrorWithCode(http.StatusConflict, "Alipay order does not match", billingErrorConflict)
	}
	now := handler.currentTime()
	input := seatProviderStatusInput{
		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), now),
	}
	switch strings.TrimSpace(result.TradeStatus) {
	case alipayTradeStatusSuccess, alipayTradeStatusFinished:
		settled, err := handler.applySeatProviderStatus(ctx, order, input)
		return settled, true, err
	case alipayTradeStatusClosed:
		seatStore, err := handler.requireSeatBilling()
		if err != nil {
			return seatOrderView{}, false, err
		}
		cancelled, err := handler.cancelSeatOrderLocally(ctx, seatStore, order, cancelledByUserID, now, map[string]string{
			"source":                "cancel_query",
			"provider_trade_no":     result.TradeNo,
			"provider_trade_status": result.TradeStatus,
			"raw_payload_sha256":    input.RawPayloadSHA256,
			"occurred_at":           now.Format(time.RFC3339),
		})
		if err != nil {
			return seatOrderView{}, false, mapBillingDependencyError(err)
		}
		return cancelled, true, nil
	case alipayTradeStatusWaitBuyerPay, "":
		return seatOrderView{}, false, nil
	default:
		return seatOrderView{}, false, api.NewHTTPErrorWithCode(http.StatusConflict, "Alipay trade status is not settleable", billingErrorProviderState)
	}
}

func (handler *Handler) cancelSeatOrderLocally(ctx context.Context, seatStore SeatBillingStore, order seatOrderView, cancelledByUserID string, now time.Time, evidence map[string]string) (seatOrderView, error) {
	return seatStore.CancelSeatOrder(ctx, seatOrderCancelParams{
		OrganizationID:    order.OrganizationID,
		OrderID:           order.ID,
		CancelledByUserID: cancelledByUserID,
		Now:               now,
		RawPayloadJSON:    seatPaymentEvidenceJSON(evidence),
	})
}

func (handler *Handler) handleSeatAlipayNotify(ctx *gin.Context) error {
	if handler == nil || !handler.enabled {
		return fmt.Errorf("billing is disabled")
	}
	if handler.alipay == nil || handler.seatBilling == nil {
		return fmt.Errorf("seat 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
	}
	providerOrderID := strings.TrimSpace(form.Get("out_trade_no"))
	if providerOrderID == "" {
		return fmt.Errorf("Alipay notify out_trade_no is required")
	}
	tradeStatus := strings.TrimSpace(form.Get("trade_status"))
	if strings.TrimSpace(form.Get("subject")) != seatPaymentSubject {
		return fmt.Errorf("Alipay notify subject does not match")
	}
	order, found, err := handler.seatBilling.LoadSeatOrderByProviderOrderID(ctx.Request.Context(), billingProviderAlipay, providerOrderID)
	if err != nil {
		return err
	}
	if !found {
		return fmt.Errorf("seat payment order not found")
	}
	occurredAt := firstNonZeroTime(
		parseAlipayFormTime(form.Get("gmt_payment")),
		parseAlipayFormTime(form.Get("gmt_close")),
		parseAlipayFormTime(form.Get("notify_time")),
		handler.currentTime(),
	)
	_, err = handler.applySeatProviderStatus(ctx.Request.Context(), order, seatProviderStatusInput{
		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
}

type billingHistoryOptions struct {
	product    string
	month      string
	monthStart time.Time
	monthEnd   time.Time
	limit      int
}

func billingHistoryOptionsFromRequest(ctx *gin.Context, now time.Time) (billingHistoryOptions, error) {
	product := strings.TrimSpace(ctx.Query("product"))
	if product == "" {
		product = billingProductAll
	}
	switch product {
	case billingProductAll, billingProductHostedCredit, billingProductSeat:
	default:
		return billingHistoryOptions{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "product is invalid", billingErrorInvalidInput)
	}
	limit := 100
	if rawLimit := strings.TrimSpace(ctx.Query("limit")); rawLimit != "" {
		parsed, err := strconv.Atoi(rawLimit)
		if err != nil || parsed < 0 {
			return billingHistoryOptions{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "limit is invalid", billingErrorInvalidInput)
		}
		limit = parsed
	}
	if limit <= 0 || limit > 100 {
		limit = 100
	}
	month, monthStart, monthEnd, err := billingMonthRange(strings.TrimSpace(ctx.Query("month")), now)
	if err != nil {
		return billingHistoryOptions{}, err
	}
	return billingHistoryOptions{product: product, month: month, monthStart: monthStart, monthEnd: monthEnd, limit: limit}, nil
}

func billingMonthRange(raw string, now time.Time) (string, time.Time, time.Time, error) {
	if now.IsZero() {
		now = time.Now().UTC()
	}
	month := strings.TrimSpace(raw)
	if month == "" {
		month = now.UTC().Format("2006-01")
	}
	start, err := time.Parse("2006-01", month)
	if err != nil {
		return "", time.Time{}, time.Time{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "month is invalid", billingErrorInvalidInput)
	}
	start = start.UTC()
	return month, start, start.AddDate(0, 1, 0), nil
}

func seatOrderListOptionsFromRequest(ctx *gin.Context, organizationID string) (seatOrderListOptions, error) {
	limit := 50
	if rawLimit := strings.TrimSpace(ctx.Query("limit")); rawLimit != "" {
		parsed, err := strconv.Atoi(rawLimit)
		if err != nil || parsed < 0 {
			return seatOrderListOptions{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "limit is invalid", billingErrorInvalidInput)
		}
		limit = parsed
	}
	if limit <= 0 || limit > 100 {
		limit = 100
	}
	statuses := make([]string, 0)
	for _, rawStatus := range ctx.QueryArray("status") {
		for _, part := range strings.Split(rawStatus, ",") {
			if status := strings.TrimSpace(part); status != "" {
				statuses = append(statuses, status)
			}
		}
	}
	return seatOrderListOptions{OrganizationID: organizationID, Limit: limit, Statuses: statuses}, nil
}

func presentSeatPlans(plans []seatPlanView) dto.BillingSeatPlansResponse {
	items := make([]dto.BillingSeatPlanResponse, 0, len(plans))
	for _, plan := range plans {
		items = append(items, dto.BillingSeatPlanResponse{
			ID:                   plan.ID,
			PlanKey:              plan.PlanKey,
			Name:                 plan.Name,
			BillingPeriod:        plan.BillingPeriod,
			UnitPriceCents:       plan.UnitPriceCents,
			Currency:             plan.Currency,
			ManagedAgentsPerSeat: plan.ManagedAgentsPerSeat,
			MinSeatQuantity:      plan.MinSeatQuantity,
			MaxSeatQuantity:      plan.MaxSeatQuantity,
			SelfServiceEnabled:   plan.SelfServiceEnabled,
			Status:               plan.Status,
		})
	}
	return dto.BillingSeatPlansResponse{Items: items}
}

func presentSeatSummary(summary seatSummaryView) dto.BillingSeatSummaryResponse {
	entitlement := summary.Entitlement
	freeHumanSeatLimit := entitlement.FreeHumanSeatLimit
	paidHumanSeatLimit := entitlement.PaidHumanSeatLimit
	giftHumanSeatLimit := entitlement.GiftHumanSeatLimit
	if freeHumanSeatLimit == 0 && paidHumanSeatLimit == 0 && giftHumanSeatLimit == 0 && entitlement.HumanSeatLimit > 0 {
		freeHumanSeatLimit = entitlement.HumanSeatLimit
	}
	return dto.BillingSeatSummaryResponse{
		OrganizationID:         entitlement.OrganizationID,
		PlanKey:                entitlement.PlanKey,
		BillingPeriod:          entitlement.BillingPeriod,
		Paid:                   entitlement.Paid,
		SubscriptionStatus:     entitlement.SubscriptionStatus,
		HumanSeatLimit:         entitlement.HumanSeatLimit,
		FreeHumanSeatLimit:     freeHumanSeatLimit,
		PaidHumanSeatLimit:     paidHumanSeatLimit,
		GiftHumanSeatLimit:     giftHumanSeatLimit,
		NextGrantExpiresAt:     entitlement.NextGrantExpiresAt,
		CurrentHumanSeats:      summary.CurrentHumanSeats,
		HumanSeatsOverLimit:    summary.HumanSeatsOverLimit,
		OrganizationAgentLimit: entitlement.OrganizationAgentLimit,
		CurrentAgents:          summary.CurrentAgents,
		AgentsOverLimit:        summary.AgentsOverLimit,
		ManagedAgentsPerSeat:   entitlement.ManagedAgentsPerSeat,
		CurrentPeriodStart:     entitlement.CurrentPeriodStart,
		CurrentPeriodEnd:       entitlement.CurrentPeriodEnd,
	}
}

func presentSeatSubscriptionEnvelope(subscription seatSubscriptionView, found bool) dto.BillingSeatSubscriptionEnvelopeResponse {
	if !found {
		return dto.BillingSeatSubscriptionEnvelopeResponse{}
	}
	presented := dto.BillingSeatSubscriptionResponse{
		OrganizationID:               subscription.OrganizationID,
		Status:                       subscription.Status,
		PlanKey:                      subscription.PlanKey,
		BillingPeriod:                subscription.BillingPeriod,
		SeatQuantity:                 subscription.SeatQuantity,
		ManagedAgentsPerSeatSnapshot: subscription.ManagedAgentsPerSeatSnapshot,
		MaxHumanSeatsSnapshot:        subscription.MaxHumanSeatsSnapshot,
		UnitPriceCentsSnapshot:       subscription.UnitPriceCentsSnapshot,
		Currency:                     subscription.Currency,
		CurrentPeriodStart:           subscription.CurrentPeriodStart,
		CurrentPeriodEnd:             subscription.CurrentPeriodEnd,
		CreatedAt:                    subscription.CreatedAt,
		UpdatedAt:                    subscription.UpdatedAt,
	}
	return dto.BillingSeatSubscriptionEnvelopeResponse{Subscription: &presented}
}

func presentSeatOrderList(orders []seatOrderView, paymentURL string) dto.BillingSeatOrderListResponse {
	items := make([]dto.BillingSeatOrderResponse, 0, len(orders))
	for _, order := range orders {
		items = append(items, presentSeatOrder(order, paymentURL))
	}
	return dto.BillingSeatOrderListResponse{Items: items}
}

func presentSeatOrder(order seatOrderView, paymentURL string) dto.BillingSeatOrderResponse {
	return dto.BillingSeatOrderResponse{
		OrderID:            order.ID,
		OrderType:          order.OrderType,
		Status:             order.Status,
		FromPlanKey:        order.FromPlanKey,
		ToPlanKey:          order.ToPlanKey,
		BillingPeriod:      order.BillingPeriod,
		SeatQuantityBefore: order.SeatQuantityBefore,
		SeatQuantityAfter:  order.SeatQuantityAfter,
		SeatDelta:          order.SeatDelta,
		AmountCents:        order.AmountCents,
		Currency:           order.Currency,
		Provider:           order.Provider,
		ProviderOrderID:    order.ProviderOrderID,
		PaymentStatus:      order.PaymentStatus,
		PaymentURL:         strings.TrimSpace(paymentURL),
		ExpiresAt:          order.ExpiresAt,
		PaidAt:             order.PaidAt,
		ClosedAt:           order.ClosedAt,
		CancelledByUserID:  order.CancelledByUserID,
		SettledAt:          order.SettledAt,
		AppliedAt:          order.AppliedAt,
		CreatedAt:          order.CreatedAt,
		UpdatedAt:          order.UpdatedAt,
	}
}

func presentHostedCreditHistoryItem(order billingPaymentOrder) dto.BillingHistoryItemResponse {
	return dto.BillingHistoryItemResponse{
		ProductType:       billingProductHostedCredit,
		SourceOrderID:     order.OrderID,
		ProviderPaymentID: order.OutTradeNo,
		Status:            order.Status,
		AmountCents:       order.PayableAmountCents,
		Currency:          order.Currency,
		CreatedAt:         order.CreatedAt,
		PaidAt:            order.PaidAt,
	}
}

func presentSeatHistoryItem(row seatPaymentHistoryView) dto.BillingHistoryItemResponse {
	return dto.BillingHistoryItemResponse{
		ProductType:       billingProductSeat,
		SourceOrderID:     row.OrderID,
		ProviderPaymentID: row.ProviderOrderID,
		Status:            row.Status,
		AmountCents:       row.AmountCents,
		Currency:          row.Currency,
		CreatedAt:         row.CreatedAt,
		PaidAt:            row.PaidAt,
	}
}

func billingHistorySortTime(item dto.BillingHistoryItemResponse) time.Time {
	if item.PaidAt != nil {
		return item.PaidAt.UTC()
	}
	return item.CreatedAt.UTC()
}

func seatPaymentEvidenceJSON(values map[string]string) string {
	normalized := make(map[string]string, len(values))
	for key, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			normalized[key] = trimmed
		}
	}
	raw, err := json.Marshal(normalized)
	if err != nil {
		return "{}"
	}
	return string(raw)
}
