package commerce

import (
	"context"
	"fmt"
	"strings"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

const (
	checkoutStockMarker   = "local_stock_checkout"
	restockMarker         = "local_stock_restock"
	provisionRefundMarker = "provision_failure_refund"
)

func checkoutInstanceState(product Product, paidAmountCents int) JSONRaw {
	return jsonRawFromAny(map[string]any{
		checkoutStockMarker: map[string]any{
			"decremented": product.StockPolicy == "tracked",
			"product_id":  product.ID,
		},
		"local_billing": map[string]any{
			"paid_amount_cents": maxInt(0, paidAmountCents),
		},
	})
}

func (s *Service) RestockReleasedInstanceTx(ctx context.Context, instanceID int64, source string, reason string, refundNo string) (bool, error) {
	var instance Instance
	if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", instanceID).First(&instance).Error; err != nil {
		return false, err
	}
	payload := jsonMap(instance.UpstreamPayload)
	if payload == nil {
		payload = map[string]any{}
	}
	if marker, exists := payload[restockMarker]; exists && marker != nil {
		return false, nil
	}

	var product Product
	if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", instance.ProductID).First(&product).Error; err != nil {
		if errorsIsRecordNotFound(err) {
			return false, nil
		}
		return false, err
	}
	if product.StockPolicy != "tracked" {
		return false, nil
	}
	decremented := true
	if checkout, ok := payload[checkoutStockMarker].(map[string]any); ok {
		if value, exists := checkout["decremented"]; exists {
			decremented = truthyAny(value)
		}
	}
	if !decremented {
		return false, nil
	}
	if err := s.db.WithContext(ctx).Model(&Product{}).Where("id = ?", product.ID).Update("stock", maxInt(0, product.Stock)+1).Error; err != nil {
		return false, err
	}
	payload[restockMarker] = map[string]any{
		"at":         time.Now().UTC().Format(time.RFC3339),
		"source":     strings.TrimSpace(source),
		"reason":     strings.TrimSpace(reason),
		"refund_no":  strings.TrimSpace(refundNo),
		"product_id": product.ID,
		"quantity":   1,
	}
	if err := s.db.WithContext(ctx).Model(&Instance{}).Where("id = ?", instance.ID).Update("upstream_payload", jsonRawFromAny(payload)).Error; err != nil {
		return false, err
	}
	return true, nil
}

func (s *Service) RestockReleasedInstance(ctx context.Context, instanceID int64, source string, reason string, refundNo string) (bool, error) {
	var restocked bool
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var err error
		restocked, err = s.withDB(tx).RestockReleasedInstanceTx(ctx, instanceID, source, reason, refundNo)
		return err
	})
	return restocked, err
}

func (s *Service) CompensateProvisionFailureTx(ctx context.Context, instanceID int64, reason string) error {
	var instance Instance
	if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", instanceID).First(&instance).Error; err != nil {
		return err
	}
	payload := jsonMap(instance.UpstreamPayload)
	if payload != nil && payload[provisionRefundMarker] != nil {
		return nil
	}
	if _, err := s.RestockReleasedInstanceTx(ctx, instance.ID, "provision_failed", reason, ""); err != nil {
		return err
	}
	if err := s.db.WithContext(ctx).Where("id = ?", instance.ID).First(&instance).Error; err != nil {
		return err
	}
	var order Order
	if err := s.db.WithContext(ctx).Where("id = ?", instance.OrderID).First(&order).Error; err != nil && !errorsIsRecordNotFound(err) {
		return err
	}
	refundAmount := maxInt(0, instance.UnitPrice)
	if billing, ok := jsonMap(instance.UpstreamPayload)["local_billing"].(map[string]any); ok {
		if paidAmount := intValueAny(billing["paid_amount_cents"], -1); paidAmount >= 0 {
			refundAmount = paidAmount
		}
	}
	if refundAmount > 0 {
		var user UserAccount
		if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", instance.UserID).First(&user).Error; err != nil {
			return err
		}
		balanceAfter := user.Balance + refundAmount
		if err := s.db.WithContext(ctx).Model(&UserAccount{}).Where("id = ?", user.ID).Update("balance", balanceAfter).Error; err != nil {
			return err
		}
		remark := "开通失败退款：" + trimText(reason, 100)
		var refNo *string
		if order.ID > 0 {
			refNo = &order.OutOrderNo
		}
		if err := s.db.WithContext(ctx).Create(&BalanceLog{UserID: user.ID, ChangeType: "order_refund", Delta: refundAmount, BalanceAfter: balanceAfter, RefNo: refNo, Remark: &remark}).Error; err != nil {
			return err
		}
		if order.ID > 0 {
			if err := s.ensureSpendEventTypeConfig(ctx, "server_purchase"); err != nil {
				return err
			}
			eventKey := fmt.Sprintf("%s:instance_%d:server_purchase_refund", order.OutOrderNo, instance.ID)
			var existing int64
			if err := s.db.WithContext(ctx).Model(&UserSpendEvent{}).Where("event_key = ?", eventKey).Count(&existing).Error; err != nil {
				return err
			}
			if existing == 0 {
				event := UserSpendEvent{UserID: user.ID, EventType: "server_purchase", EventKey: eventKey, AmountCents: -refundAmount, OrderID: &order.ID, RefNo: &order.OutOrderNo, Remark: &remark, PayloadSnapshot: jsonRawFromAny(map[string]any{"instance_id": instance.ID})}
				if err := s.db.WithContext(ctx).Create(&event).Error; err != nil {
					return err
				}
			}
			if err := s.recalculateMembershipSpent(ctx, user.ID); err != nil {
				return err
			}
		}
	}
	if err := s.reverseInstancePointRewards(ctx, instance.ID, reason); err != nil {
		return err
	}
	payload = jsonMap(instance.UpstreamPayload)
	if payload == nil {
		payload = map[string]any{}
	}
	payload[provisionRefundMarker] = map[string]any{"at": time.Now().UTC().Format(time.RFC3339), "amount_cents": refundAmount, "reason": trimText(reason, 500)}
	return s.db.WithContext(ctx).Model(&Instance{}).Where("id = ?", instance.ID).Update("upstream_payload", jsonRawFromAny(payload)).Error
}

func (s *Service) reverseInstancePointRewards(ctx context.Context, instanceID int64, reason string) error {
	var rewards []PointReward
	if err := s.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("instance_id = ? AND status IN ?", instanceID, []string{"pending", "claimable", "claimed"}).Find(&rewards).Error; err != nil {
		return err
	}
	now := time.Now().UTC()
	for i := range rewards {
		reward := &rewards[i]
		if reward.Status == "claimed" {
			refNo := valueOrDefault(reward.RefNo, fmt.Sprintf("reward:%d", reward.ID)) + ":provision_reverse"
			log, err := s.addPoints(ctx, reward.UserID, -reward.Points, "order_refund", reward.OrderID, refNo, "开通失败扣回积分："+trimText(reason, 100))
			if err != nil {
				return err
			}
			reward.Status = "reversed"
			reward.ReversePointLogID = &log.ID
			reward.ReversedAt = &now
		} else {
			reward.Status = "cancelled"
			reward.CancelledAt = &now
		}
		reward.Remark = &reason
		if err := s.db.WithContext(ctx).Save(reward).Error; err != nil {
			return err
		}
	}
	return nil
}
