package mysql

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
	"strings"
	"time"
)

// GrantVip 是活动和后台赠送 VIP 的统一账务入口；它不扣金币，也不检查充值门槛。
func (r *Repository) GrantVip(ctx context.Context, command ledger.GrantVipCommand) (ledger.GrantVipReceipt, error) {
	if r == nil || r.db == nil {
		return ledger.GrantVipReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, command.AppCode)
	command.AppCode = appcode.FromContext(ctx)
	command.GrantSource = ledger.NormalizeVipGrantSource(command.GrantSource)
	if command.GrantSource == "" || command.GrantSource == ledger.VipGrantSourcePurchase {
		return ledger.GrantVipReceipt{}, xerr.New(xerr.InvalidArgument, "vip grant_source is invalid")
	}
	command.Reason = strings.TrimSpace(command.Reason)
	if command.Reason == "" {
		command.Reason = command.GrantSource
	}

	requestHash := vipGrantRequestHash(command)
	nowMs := time.Now().UnixMilli()
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	defer func() { _ = tx.Rollback() }()

	if err := r.lockVipCommand(ctx, tx, command.CommandID, bizTypeVIPGrant, requestHash, nowMs, xerr.LedgerConflict); err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	if _, exists, err := r.lookupTransactionConsistent(ctx, tx, command.CommandID, requestHash, bizTypeVIPGrant, xerr.LedgerConflict); err != nil || exists {
		if err != nil || !exists {
			return ledger.GrantVipReceipt{}, err
		}
		_ = tx.Rollback()
		return r.replayVipGrant(ctx, command, requestHash)
	}

	program, err := r.queryVipProgramConfig(ctx, tx.QueryRowContext(ctx, vipProgramSelectSQL()+` LOCK IN SHARE MODE`, command.AppCode))
	if errors.Is(err, sql.ErrNoRows) {
		return ledger.GrantVipReceipt{}, xerr.New(xerr.NotFound, "vip program config not found")
	}
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	if program.Status != ledger.VipStatusActive {
		return ledger.GrantVipReceipt{}, xerr.New(xerr.Conflict, "vip program is disabled")
	}
	if command.Level > program.LevelCount {
		return ledger.GrantVipReceipt{}, xerr.New(xerr.VIPLevelNotFound, "vip level exceeds program level_count")
	}
	// 体验卡体系的后台发放必须走 GrantVipTrialCard，禁止旧 GrantVip 绕过背包佩戴语义直接改会员表。
	if program.GrantMode != ledger.VipGrantModeDirectMembership {
		return ledger.GrantVipReceipt{}, xerr.New(xerr.Conflict, "vip program requires trial card grant")
	}
	level, err := r.getVipLevelForUpdate(ctx, tx, command.Level)
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	if level.Status != ledger.VipStatusActive {
		return ledger.GrantVipReceipt{}, xerr.New(xerr.VIPLevelDisabled, "vip level is disabled")
	}
	if command.GrantSource == ledger.VipGrantSourceManagerCenter {

		level.DurationMS = managerCenterVIPGrantDurationMS
	}
	current, err := r.ensureUserVipForUpdate(ctx, tx, command.TargetUserID, nowMs)
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	expiryPolicy := program.SameLevelExpiryPolicy
	if current.Active && command.Level > current.Level {
		expiryPolicy = program.UpgradeExpiryPolicy
	}
	expectedStartedAtMS, expectedExpiresAtMS, err := vipActivationWindow(current, level, expiryPolicy, nowMs)
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	transactionID := transactionID(command.AppCode, command.CommandID)
	recordID := "vip_grant_" + stableHash(command.AppCode+"|"+command.CommandID)
	expectedResourceGrantID := resourceGrantID(command.AppCode, boundedDerivedCommandID("vip_reward:", command.CommandID))
	metadata := map[string]any{
		"app_code":                   command.AppCode,
		"grant_id":                   recordID,
		"resource_grant_id":          expectedResourceGrantID,
		"user_id":                    command.TargetUserID,
		"target_level":               command.Level,
		"vip_name":                   level.Name,
		"grant_source":               command.GrantSource,
		"operator_user_id":           command.OperatorUserID,
		"reason":                     command.Reason,
		"reward_group_id":            level.RewardResourceGroupID,
		"duration_ms":                level.DurationMS,
		"transaction_id":             transactionID,
		"required_recharge_bypassed": true,
		"program_type":               program.ProgramType,
		"config_version":             program.ConfigVersion,
		"expiry_policy":              expiryPolicy,
		"previous_expires_at_ms":     current.ExpiresAtMS,
		"started_at_ms":              expectedStartedAtMS,
		"new_expires_at_ms":          expectedExpiresAtMS,
		"created_at_ms":              nowMs,
	}
	if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeVIPGrant, requestHash, recordID, metadata, nowMs); err != nil {
		if isMySQLDuplicateError(err) {
			_ = tx.Rollback()
			return r.replayVipGrant(ctx, command, requestHash)
		}
		return ledger.GrantVipReceipt{}, err
	}
	activation, err := r.activateUserVip(ctx, tx, vipActivationCommand{
		AppCode:        command.AppCode,
		CommandID:      command.CommandID,
		UserID:         command.TargetUserID,
		Source:         command.GrantSource,
		Reason:         command.Reason,
		OperatorUserID: command.OperatorUserID,
		TransactionID:  transactionID,
		RecordID:       recordID,
		Level:          level,
		Current:        current,
		Program:        program,
		ExpiryPolicy:   expiryPolicy,
		NowMS:          nowMs,
	})
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
		vipActivatedEvent(transactionID, command.CommandID, command.TargetUserID, command.GrantSource, activation.Action, activation.PreviousLevel, activation.PreviousExpiresAtMS, activation.ExpiryPolicy, activation.VIP, level.RewardResourceGroupID, activation.ResourceGrantID, activation.RewardItems, nowMs),
	}); err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	if err := tx.Commit(); err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	state, err := r.GetVipState(ctx, command.TargetUserID)
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	return ledger.GrantVipReceipt{
		TransactionID: transactionID,
		Vip:           activation.VIP,
		RewardItems:   activation.RewardItems,
		State:         state,
	}, nil
}

func (r *Repository) replayVipGrant(ctx context.Context, command ledger.GrantVipCommand, requestHash string) (ledger.GrantVipReceipt, error) {
	tx, err := r.db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true, Isolation: sql.LevelRepeatableRead})
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	defer func() { _ = tx.Rollback() }()
	txRow, exists, err := r.lookupTransactionConsistent(ctx, tx, command.CommandID, requestHash, bizTypeVIPGrant, xerr.LedgerConflict)
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	if !exists {
		return ledger.GrantVipReceipt{}, xerr.New(xerr.Unavailable, "vip grant idempotent receipt is not visible")
	}
	receipt, err := r.vipGrantReceiptForTransaction(ctx, tx, txRow, command.TargetUserID)
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	_ = tx.Rollback()
	receipt.State, err = r.GetVipState(ctx, command.TargetUserID)
	return receipt, err
}

func (r *Repository) vipGrantReceiptForTransaction(ctx context.Context, tx *sql.Tx, txRow transactionRow, userID int64) (ledger.GrantVipReceipt, error) {
	var metadata struct {
		ResourceGrantID string `json:"resource_grant_id"`
		TargetLevel     int32  `json:"target_level"`
		VIPName         string `json:"vip_name"`
		StartedAtMS     int64  `json:"started_at_ms"`
		NewExpiresAtMS  int64  `json:"new_expires_at_ms"`
		ProgramType     string `json:"program_type"`
		ConfigVersion   int64  `json:"config_version"`
		CreatedAtMS     int64  `json:"created_at_ms"`
	}
	_ = json.Unmarshal([]byte(txRow.MetadataJSON), &metadata)
	vip := ledger.UserVip{
		UserID:        userID,
		Level:         metadata.TargetLevel,
		Name:          metadata.VIPName,
		Active:        metadata.TargetLevel > 0 && metadata.NewExpiresAtMS > time.Now().UnixMilli(),
		StartedAtMS:   metadata.StartedAtMS,
		ExpiresAtMS:   metadata.NewExpiresAtMS,
		UpdatedAtMS:   metadata.CreatedAtMS,
		ProgramType:   metadata.ProgramType,
		ConfigVersion: metadata.ConfigVersion,
	}
	if vip.Level <= 0 || vip.ExpiresAtMS <= 0 {
		// 迁移前的历史赠送元数据没有完整快照，只能兼容读取当前会员；新命令始终走上面的稳定回执。
		var err error
		vip, err = r.queryUserVip(ctx, tx, userID, time.Now().UnixMilli(), false)
		if err != nil {
			return ledger.GrantVipReceipt{}, err
		}
	}
	rewards, err := r.rewardItemsByGrant(ctx, tx, metadata.ResourceGrantID, vip.ExpiresAtMS)
	if err != nil {
		return ledger.GrantVipReceipt{}, err
	}
	return ledger.GrantVipReceipt{
		TransactionID: txRow.TransactionID,
		Vip:           vip,
		RewardItems:   rewards,
	}, nil
}

func vipGrantRequestHash(command ledger.GrantVipCommand) string {
	return stableHash(fmt.Sprintf("vip_grant|%s|%d|%d|%s|%d|%s",
		appcode.Normalize(command.AppCode),
		command.TargetUserID,
		command.Level,
		ledger.NormalizeVipGrantSource(command.GrantSource),
		command.OperatorUserID,
		strings.TrimSpace(command.Reason),
	))
}
