package billing

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"math/big"
	"strings"
	"unicode"

	"juhe-factory/api/internal/model"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

const (
	TextBillingPerRequest = "per_request"
	TextBillingPerToken   = "per_token"
)

type TextPricing struct {
	Mode       string `json:"mode"`
	PerRequest string `json:"per_request,omitempty"`
	InputPerM  string `json:"input_per_m,omitempty"`
	OutputPerM string `json:"output_per_m,omitempty"`
}

type TextUsage struct {
	Input  int64
	Output int64
}

func CreateTextGenerationTask(tx *gorm.DB, task *model.GenerationTask, pricing TextPricing, remark string) error {
	if err := pricing.Validate(); err != nil {
		return err
	}
	task.BillingSnapshot = pricing.MarshalSnapshot()
	if pricing.Mode == TextBillingPerRequest {
		return PrechargeGenerationTask(tx, task, pricing.PerRequest, 1, remark)
	}
	task.EstimatedPoints = "0.00"
	task.PrepaidPoints = "0.00"
	if task.ID == uuid.Nil {
		task.ID = uuid.New()
	}
	return tx.Create(task).Error
}

func ChargeTextRequest(tx *gorm.DB, userID uuid.UUID, referenceID string, pricing TextPricing, remark string) (string, error) {
	if pricing.Mode != TextBillingPerRequest {
		return "", errors.New("文本模型不是按次计费")
	}
	businessID := textCallBusinessID(referenceID, "request")
	idempotencyKey := "generation:hold:" + businessID
	if existing, found, err := debitAmount(tx, idempotencyKey); err != nil {
		return "", err
	} else if found {
		return existing, nil
	}
	if pricing.PerRequest == "0" || pricing.PerRequest == "0.00" || pricing.PerRequest == "0.0000" {
		return "0.00", nil
	}
	_, err := DebitPoints(tx, userID, pricing.PerRequest, "generation_hold", businessID, remark, idempotencyKey)
	return pricing.PerRequest, err
}

func RefundTextGenerationTask(tx *gorm.DB, task *model.GenerationTask, remark string) (bool, error) {
	pricing, err := ParseTextPricingSnapshot(task.BillingSnapshot)
	if err != nil || pricing.Mode == TextBillingPerRequest {
		return RefundGenerationTask(tx, task, remark)
	}
	if task.CostRefunded {
		return false, nil
	}
	refunded, err := RefundTextCalls(tx, task.UserID, task.ID.String(), remark)
	if err == nil {
		task.CostRefunded = true
	}
	return refunded, err
}

func LoadTextPricing(db *gorm.DB, modelID uuid.UUID) (TextPricing, error) {
	var pricing TextPricing
	err := db.Table("models m").
		Select(`coalesce(m.text_billing_mode,'') AS mode,
			coalesce(max(CASE WHEN lower(p.price_key)='default' THEN p.price::text END),'') AS per_request,
			coalesce(max(CASE WHEN lower(p.price_key)='input' THEN p.price::text END),'') AS input_per_m,
			coalesce(max(CASE WHEN lower(p.price_key)='output' THEN p.price::text END),'') AS output_per_m`).
		Joins("LEFT JOIN model_prices p ON p.model_id=m.id").
		Where("m.id=? AND m.model_type='text' AND m.enabled=true AND m.deleted_at IS NULL", modelID).
		Group("m.id,m.text_billing_mode").
		Take(&pricing).Error
	if err != nil {
		return pricing, err
	}
	if err := pricing.Validate(); err != nil {
		return pricing, err
	}
	return pricing, nil
}

func (pricing TextPricing) Validate() error {
	switch pricing.Mode {
	case TextBillingPerRequest:
		if !validNonNegativeDecimal(pricing.PerRequest) {
			return errors.New("文本模型未配置按次价格")
		}
	case TextBillingPerToken:
		if !validNonNegativeDecimal(pricing.InputPerM) || !validNonNegativeDecimal(pricing.OutputPerM) {
			return errors.New("文本模型未完整配置输入、输出 Token 价格")
		}
	default:
		return errors.New("文本模型计费模式无效")
	}
	return nil
}

func (pricing TextPricing) MarshalSnapshot() json.RawMessage {
	data, _ := json.Marshal(pricing)
	return data
}

func ParseTextPricingSnapshot(value json.RawMessage) (TextPricing, error) {
	var pricing TextPricing
	if len(value) == 0 || string(value) == "{}" {
		return pricing, errors.New("文本模型计费快照缺失")
	}
	if err := json.Unmarshal(value, &pricing); err != nil {
		return pricing, errors.New("文本模型计费快照无效")
	}
	return pricing, pricing.Validate()
}

func CalculateTextPoints(pricing TextPricing, usage TextUsage) (string, error) {
	if pricing.Mode != TextBillingPerToken || usage.Input < 0 || usage.Output < 0 {
		return "", errors.New("Token 计费参数无效")
	}
	inputRate, ok := new(big.Rat).SetString(pricing.InputPerM)
	if !ok || inputRate.Sign() < 0 {
		return "", errors.New("输入 Token 价格无效")
	}
	outputRate, ok := new(big.Rat).SetString(pricing.OutputPerM)
	if !ok || outputRate.Sign() < 0 {
		return "", errors.New("输出 Token 价格无效")
	}
	amount := new(big.Rat).Mul(inputRate, new(big.Rat).SetInt64(usage.Input))
	amount.Add(amount, new(big.Rat).Mul(outputRate, new(big.Rat).SetInt64(usage.Output)))
	amount.Quo(amount, big.NewRat(1000000, 1))
	return ceilPointCents(amount), nil
}

func EstimateTextTokens(value string) int64 {
	var tokens int64
	ascii := 0
	flush := func() {
		if ascii > 0 {
			tokens += int64((ascii + 3) / 4)
			ascii = 0
		}
	}
	for _, r := range value {
		if r <= unicode.MaxASCII {
			ascii++
		} else {
			flush()
			tokens++
		}
	}
	flush()
	if tokens == 0 && value != "" {
		return 1
	}
	return tokens
}

func ReserveTextCall(tx *gorm.DB, userID uuid.UUID, referenceID, callKey string, pricing TextPricing, estimatedInputTokens int64, remark string) (string, error) {
	if pricing.Mode != TextBillingPerToken || estimatedInputTokens < 0 {
		return "", errors.New("Token 预授权参数无效")
	}
	amount, err := CalculateTextReservePoints(pricing, estimatedInputTokens)
	if err != nil {
		return "", err
	}
	businessID := textCallBusinessID(referenceID, callKey) + ":reserve"
	idempotencyKey := "text:reserve:" + businessID
	if existing, found, err := debitAmount(tx, idempotencyKey); err != nil {
		return "", err
	} else if found {
		return existing, nil
	}
	if amount == "0.00" {
		return amount, nil
	}
	_, err = DebitPoints(tx, userID, amount, "text_token_reserve", businessID, remark+"预授权", idempotencyKey)
	return amount, err
}

func CalculateTextReservePoints(pricing TextPricing, estimatedInputTokens int64) (string, error) {
	if estimatedInputTokens < 0 || estimatedInputTokens > math.MaxInt64/2 {
		return "", errors.New("Token 预授权参数无效")
	}
	return CalculateTextPoints(pricing, TextUsage{Input: estimatedInputTokens, Output: estimatedInputTokens * 2})
}

func SettleTextCall(tx *gorm.DB, userID uuid.UUID, referenceID, callKey string, pricing TextPricing, usage TextUsage, remark string) (string, error) {
	if usage.Input <= 0 && usage.Output <= 0 {
		return "", errors.New("上游未返回 Token 用量，无法完成计费")
	}
	actual, err := CalculateTextPoints(pricing, usage)
	if err != nil {
		return "", err
	}
	baseID := textCallBusinessID(referenceID, callKey)
	reserveID := baseID + ":reserve"
	if _, found, err := debitAmount(tx, "text:reserve:"+reserveID); err != nil {
		return "", err
	} else if found {
		if _, err := RefundDebit(tx, userID, "text:reserve:"+reserveID, "text:reserve-refund:"+reserveID,
			"text_token_reserve_refund", reserveID, remark+"预授权释放"); err != nil {
			return "", err
		}
	}
	chargeKey := "generation:hold:" + baseID
	if existing, found, err := debitAmount(tx, chargeKey); err != nil {
		return "", err
	} else if found {
		return existing, nil
	}
	if actual == "0.00" {
		return actual, nil
	}
	_, err = DebitPoints(tx, userID, actual, "generation_hold", baseID, remark, chargeKey)
	return actual, err
}

func RefundTextCalls(tx *gorm.DB, userID uuid.UUID, referenceID, remark string) (bool, error) {
	type debit struct {
		BusinessType   string
		BusinessID     string
		IdempotencyKey string
	}
	debits := make([]debit, 0)
	if err := tx.Table("point_ledger").
		Select("business_type,business_id,idempotency_key").
		Where("user_id=? AND change_amount<0 AND business_id LIKE ? AND business_type IN ?", userID, referenceID+":text:%", []string{"generation_hold", "text_token_reserve"}).
		Order("id").Find(&debits).Error; err != nil {
		return false, err
	}
	refunded := false
	for _, debit := range debits {
		refundKey := "generation:refund:" + debit.BusinessID
		refundType := "generation_refund"
		if debit.BusinessType == "text_token_reserve" {
			refundKey = "text:reserve-refund:" + debit.BusinessID
			refundType = "text_token_reserve_refund"
		}
		created, err := RefundDebit(tx, userID, debit.IdempotencyKey, refundKey, refundType, debit.BusinessID, remark)
		if err != nil {
			return refunded, err
		}
		refunded = refunded || created
	}
	return refunded, nil
}

func CommitTextReserves(tx *gorm.DB, userID uuid.UUID, referenceID, remark string) (string, error) {
	type reserve struct {
		BusinessID     string
		IdempotencyKey string
	}
	items := make([]reserve, 0)
	if err := tx.Table("point_ledger").Select("business_id,idempotency_key").
		Where("user_id=? AND business_type='text_token_reserve' AND business_id LIKE ? AND NOT EXISTS (SELECT 1 FROM point_ledger refund WHERE refund.idempotency_key='text:reserve-refund:'||point_ledger.business_id)", userID, referenceID+":text:%").
		Find(&items).Error; err != nil {
		return "", err
	}
	for _, item := range items {
		baseID := strings.TrimSuffix(item.BusinessID, ":reserve")
		if err := tx.Exec("UPDATE point_ledger SET business_type='generation_hold',business_id=?,idempotency_key=?,remark=? WHERE idempotency_key=?", baseID, "generation:hold:"+baseID, remark, item.IdempotencyKey).Error; err != nil {
			return "", err
		}
	}
	var amount string
	if err := tx.Table("point_ledger").Select("coalesce(sum(-change_amount),0)::text").
		Where("user_id=? AND business_type='generation_hold' AND business_id LIKE ?", userID, referenceID+":text:%").Scan(&amount).Error; err != nil {
		return "", err
	}
	return amount, nil
}

func validNonNegativeDecimal(value string) bool {
	number, ok := new(big.Rat).SetString(strings.TrimSpace(value))
	return ok && number.Sign() >= 0
}

func ceilPointCents(value *big.Rat) string {
	if value == nil || value.Sign() <= 0 {
		return "0.00"
	}
	cents := new(big.Rat).Mul(value, big.NewRat(100, 1))
	quotient := new(big.Int).Quo(cents.Num(), cents.Denom())
	if new(big.Int).Mod(cents.Num(), cents.Denom()).Sign() > 0 {
		quotient.Add(quotient, big.NewInt(1))
	}
	return fmt.Sprintf("%d.%02d", new(big.Int).Quo(quotient, big.NewInt(100)), new(big.Int).Mod(quotient, big.NewInt(100)))
}

func textCallBusinessID(referenceID, callKey string) string {
	return referenceID + ":text:" + strings.TrimSpace(callKey)
}

func debitAmount(tx *gorm.DB, idempotencyKey string) (string, bool, error) {
	var amount string
	result := tx.Table("point_ledger").Select("(-change_amount)::text").Where("idempotency_key=? AND change_amount<0", idempotencyKey).Limit(1).Scan(&amount)
	if result.Error != nil {
		return "", false, result.Error
	}
	return amount, strings.TrimSpace(amount) != "", nil
}
