package biz

import (
	"context"
	"encoding/json"
	"errors"
	"strings"
	"sync"
	"time"
)

const (
	PaymentStatusInitialized       = "initialized"
	PaymentStatusPending           = "pending"
	PaymentStatusPaid              = "paid"
	PaymentStatusFailed            = "failed"
	PaymentStatusClosed            = "closed"
	PaymentStatusPartiallyRefunded = "partially_refunded"
	PaymentStatusRefunded          = "refunded"

	FulfillmentStatusPending    = "pending"
	FulfillmentStatusProcessing = "processing"
	FulfillmentStatusSucceeded  = "succeeded"
	FulfillmentStatusFailed     = "failed"

	RefundStatusNone       = "none"
	RefundStatusProcessing = "processing"
	RefundStatusPending    = "pending"
	RefundStatusPartial    = "partial"
	RefundStatusSucceeded  = "succeeded"
	RefundStatusFailed     = "failed"
)

var (
	ErrPaymentOrderNotFound    = errors.New("支付订单不存在")
	ErrPaymentOrderConflict    = errors.New("支付订单参数冲突")
	ErrPaymentOrderBusy        = errors.New("支付订单正在处理中")
	ErrPaymentOrderState       = errors.New("支付订单状态不允许当前操作")
	ErrPaymentProviderConflict = errors.New("支付平台交易号冲突")
)

// PaymentOrder is one payment attempt. Business modules own their domain
// orders and link to this record through BusinessType and BusinessID.
type PaymentOrder struct {
	ID                     uint64
	TradeNo                string
	Provider               string
	ProviderTradeNo        string
	QueryID                string
	BusinessType           string
	BusinessID             string
	Subject                string
	PaymentMode            string
	OriginalAmount         int64
	Amount                 int64
	PaidAmount             int64
	PayerPaidAmount        int64
	CashPaidAmount         int64
	PointPaidAmount        int64
	DiscountAmount         int64
	ProviderDiscountAmount int64
	MerchantDiscountAmount int64
	SettlementAmount       int64
	Currency               string
	PayerCurrency          string
	AmountBreakdownKnown   bool
	PaymentStatus          string
	ProviderStatus         string
	FulfillmentStatus      string
	RefundStatus           string
	RefundedAmount         int64
	RefundRequestedAmount  int64
	RefundNo               string
	ConfirmationID         string
	RequestFingerprint     string
	CreatePayload          json.RawMessage
	Extra                  json.RawMessage
	LastEventID            string
	LastPayloadHash        string
	LastError              string
	FulfillmentToken       string
	RefundToken            string
	Version                uint64
	CreatedAt              time.Time
	UpdatedAt              time.Time
	PaidAt                 *time.Time
	FulfilledAt            *time.Time
	RefundedAt             *time.Time
	FulfillmentLeaseUntil  *time.Time
	RefundLeaseUntil       *time.Time
}

type PaymentProviderUpdate struct {
	Status                 string
	ProviderStatus         string
	ProviderTradeNo        string
	QueryID                string
	Amount                 int64
	PayerPaidAmount        int64
	CashPaidAmount         int64
	PointPaidAmount        int64
	DiscountAmount         int64
	ProviderDiscountAmount int64
	MerchantDiscountAmount int64
	SettlementAmount       int64
	Currency               string
	PayerCurrency          string
	AmountBreakdownKnown   bool
	EventID                string
	PayloadHash            string
	CreatePayload          json.RawMessage
}

type PaymentOrderFilter struct {
	Provider      string
	TradeNo       string
	BusinessType  string
	BusinessID    string
	PaymentStatus string
	RefundStatus  string
}

type PaymentOrderRepo interface {
	CreatePaymentOrder(context.Context, *PaymentOrder) (order *PaymentOrder, created bool, err error)
	FindPaymentOrder(context.Context, string, string) (*PaymentOrder, error)
	ListPaymentOrders(context.Context, int, int, PaymentOrderFilter) ([]*PaymentOrder, int64, error)
	RecordPaymentCreate(context.Context, string, string, *PaymentProviderUpdate) (*PaymentOrder, error)
	ApplyPaymentResult(context.Context, string, string, *PaymentProviderUpdate) (*PaymentOrder, error)
	BeginPaymentFulfillment(context.Context, string, string, time.Duration) (order *PaymentOrder, token string, duplicate bool, err error)
	CompletePaymentFulfillment(context.Context, string, string, string, bool, string) (*PaymentOrder, error)
	BeginPaymentRefund(context.Context, string, string, int64, time.Duration) (order *PaymentOrder, token string, err error)
	CompletePaymentRefundRequest(context.Context, string, string, string, bool, string) (*PaymentOrder, error)
	ConfirmPaymentRefund(context.Context, string, string, string, int64, bool, string) (*PaymentOrder, error)
}

// PaymentOrderSource is implemented by each business module. It returns the
// canonical order facts; client-provided amount, currency and subject are not
// trusted once the persistent payment-order flow is enabled.
type PaymentOrderSource interface {
	Type() string
	PreparePayment(context.Context, string, string, string) (*PaymentIntent, error)
}

type PaymentBusinessModule interface {
	PaymentOrderSource
	PaymentFulfillmentHandler
	PaymentRefundAuthorizer
}

type PaymentRefundAuthorizer interface {
	AuthorizeRefund(context.Context, *PaymentOrder, int64) error
}

type PaymentOrderSourceRegistry struct {
	mu      sync.RWMutex
	sources map[string]PaymentOrderSource
}

func NewPaymentOrderSourceRegistry() *PaymentOrderSourceRegistry {
	return &PaymentOrderSourceRegistry{sources: map[string]PaymentOrderSource{}}
}

func (r *PaymentOrderSourceRegistry) Register(source PaymentOrderSource) error {
	if source == nil || strings.TrimSpace(source.Type()) == "" {
		return errors.New("支付业务订单来源无效")
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	if _, exists := r.sources[source.Type()]; exists {
		return errors.New("支付业务订单来源已注册: " + source.Type())
	}
	r.sources[source.Type()] = source
	return nil
}

func (r *PaymentOrderSourceRegistry) Source(kind string) PaymentOrderSource {
	if r == nil {
		return nil
	}
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.sources[kind]
}

func (r *PaymentOrderSourceRegistry) Unregister(kind string) {
	if r == nil {
		return
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	delete(r.sources, kind)
}

func (o *PaymentOrder) Intent() *PaymentIntent {
	if o == nil {
		return nil
	}
	return &PaymentIntent{
		Provider:       o.Provider,
		TradeNo:        o.TradeNo,
		BusinessType:   o.BusinessType,
		BusinessID:     o.BusinessID,
		Subject:        o.Subject,
		Amount:         o.Amount,
		Currency:       o.Currency,
		Extra:          append(json.RawMessage(nil), o.Extra...),
		PaymentMode:    defaultPaymentMode(o.Provider, o.PaymentMode),
		OriginalAmount: o.OriginalAmount,
	}
}

func defaultPaymentMode(provider, mode string) string {
	if strings.TrimSpace(mode) != "" {
		return mode
	}
	if provider == PaymentInternal {
		return PaymentModeInternal
	}
	return PaymentModeExternal
}
