package mysql

import (
	"context"
	"database/sql"
	"errors"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
	resourcedomain "hyapp/services/wallet-service/internal/domain/resource"
	"time"
)

// GetMyVip 返回用户当前 VIP 状态；过期只影响 active 投影，不直接修改历史事实。
func (r *Repository) GetMyVip(ctx context.Context, userID int64) (ledger.UserVip, error) {
	if r == nil || r.db == nil {
		return ledger.UserVip{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	vip, err := r.queryUserVip(ctx, r.db, userID, time.Now().UnixMilli(), false)
	if errors.Is(err, sql.ErrNoRows) {
		return ledger.UserVip{UserID: userID}, nil
	}
	return vip, err
}

// ListVipPackages 返回当前会员和全部 active VIP 包。
func (r *Repository) ListVipPackages(ctx context.Context, userID int64) (ledger.UserVip, []ledger.VipLevel, error) {
	if r == nil || r.db == nil {
		return ledger.UserVip{}, nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	nowMs := time.Now().UnixMilli()
	current, err := r.GetMyVip(ctx, userID)
	if err != nil {
		return ledger.UserVip{}, nil, err
	}
	totalRechargeCoin, err := r.userRechargeCoinAmount(ctx, r.db, userID)
	if err != nil {
		return ledger.UserVip{}, nil, err
	}
	levels, err := r.listVipLevels(ctx, nowMs, current.Level, totalRechargeCoin)
	return current, levels, err
}

// ListAdminVipLevels 返回后台配置页所需的全部 VIP 等级。
func (r *Repository) ListAdminVipLevels(ctx context.Context) ([]ledger.VipLevel, error) {
	if r == nil || r.db == nil {
		return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	rows, err := r.db.QueryContext(ctx, `
		SELECT level, name, status, price_coin, duration_ms, reward_resource_group_id,
		       required_recharge_coin_amount, sort_order, created_at_ms, updated_at_ms
		FROM vip_levels
		WHERE app_code = ?
		ORDER BY level ASC`,
		appcode.FromContext(ctx),
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	levels := make([]ledger.VipLevel, 0, 10)
	for rows.Next() {
		level, err := r.scanVipLevel(rows)
		if err != nil {
			return nil, err
		}
		if level.RewardResourceGroupID > 0 {
			level.RewardItems, err = r.vipRewardItems(ctx, level.RewardResourceGroupID, time.Now().UnixMilli()+level.DurationMS)
			if err != nil {
				return nil, err
			}
		}
		levels = append(levels, level)
	}
	return levels, rows.Err()
}

func (r *Repository) listVipLevels(ctx context.Context, nowMs int64, currentLevel int32, totalRechargeCoin int64) ([]ledger.VipLevel, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT level, name, status, price_coin, duration_ms, reward_resource_group_id,
		       required_recharge_coin_amount, sort_order, created_at_ms, updated_at_ms
		FROM vip_levels
		WHERE app_code = ? AND status = ?
		ORDER BY sort_order ASC, level ASC`,
		appcode.FromContext(ctx), ledger.VipStatusActive,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	levels := make([]ledger.VipLevel, 0)
	for rows.Next() {
		level, err := r.scanVipLevel(rows)
		if err != nil {
			return nil, err
		}
		level.UserRechargeCoinAmount = totalRechargeCoin
		level.RechargeGateRequired = level.Level >= 6
		level.CanPurchase = currentLevel == 0 || level.Level >= currentLevel
		if !level.CanPurchase {
			level.PurchaseLockedReason = "current_vip_higher"
		}
		if level.CanPurchase && level.RechargeGateRequired && totalRechargeCoin < level.RequiredRechargeCoinAmount {
			level.CanPurchase = false
			level.PurchaseLockedReason = "recharge_required"
		}
		level.RewardItems, err = r.vipRewardItems(ctx, level.RewardResourceGroupID, nowMs+level.DurationMS)
		if err != nil {
			return nil, err
		}
		levels = append(levels, level)
	}
	return levels, rows.Err()
}

func (r *Repository) scanVipLevel(scanner scanTarget) (ledger.VipLevel, error) {
	var level ledger.VipLevel
	err := scanner.Scan(
		&level.Level,
		&level.Name,
		&level.Status,
		&level.PriceCoin,
		&level.DurationMS,
		&level.RewardResourceGroupID,
		&level.RequiredRechargeCoinAmount,
		&level.SortOrder,
		&level.CreatedAtMS,
		&level.UpdatedAtMS,
	)
	level.RechargeGateRequired = level.Level >= 6
	return level, err
}

func (r *Repository) userRechargeCoinAmount(ctx context.Context, querier interface {
	QueryRowContext(context.Context, string, ...any) *sql.Row
}, userID int64) (int64, error) {
	var total int64
	err := querier.QueryRowContext(ctx, `
		SELECT total_coin_amount
		FROM wallet_user_recharge_stats
		WHERE app_code = ? AND user_id = ?`,
		appcode.FromContext(ctx), userID,
	).Scan(&total)
	if errors.Is(err, sql.ErrNoRows) {
		return 0, nil
	}
	return total, err
}

func (r *Repository) ensureUserVipForUpdate(ctx context.Context, tx *sql.Tx, userID int64, nowMs int64) (ledger.UserVip, error) {
	if _, err := tx.ExecContext(ctx, `
		INSERT IGNORE INTO user_vip_memberships (
		   app_code, user_id, level, name, status, started_at_ms, expires_at_ms, created_at_ms, updated_at_ms
		 ) VALUES (?, ?, 0, '', 'inactive', 0, 0, ?, ?)`,
		appcode.FromContext(ctx), userID, nowMs, nowMs,
	); err != nil {
		return ledger.UserVip{}, err
	}
	return r.queryUserVip(ctx, tx, userID, nowMs, true)
}

func (r *Repository) queryUserVip(ctx context.Context, querier interface {
	QueryRowContext(context.Context, string, ...any) *sql.Row
}, userID int64, nowMs int64, lock bool) (ledger.UserVip, error) {
	query := `
		SELECT m.user_id, m.level, COALESCE(v.name, m.name), m.started_at_ms, m.expires_at_ms, m.updated_at_ms
		FROM user_vip_memberships m
		LEFT JOIN vip_levels v ON v.app_code = m.app_code AND v.level = m.level
		WHERE m.app_code = ? AND m.user_id = ?`
	if lock {
		query += ` FOR UPDATE`
	}
	var vip ledger.UserVip
	if err := querier.QueryRowContext(ctx, query, appcode.FromContext(ctx), userID).Scan(&vip.UserID, &vip.Level, &vip.Name, &vip.StartedAtMS, &vip.ExpiresAtMS, &vip.UpdatedAtMS); err != nil {
		return ledger.UserVip{}, err
	}
	vip.Active = vip.Level > 0 && vip.ExpiresAtMS > nowMs
	if !vip.Active {
		vip.Level = 0
		vip.Name = ""
	}
	return vip, nil
}

func (r *Repository) vipRewardItems(ctx context.Context, groupID int64, expiresAtMS int64) ([]ledger.VipRewardItem, error) {
	if groupID <= 0 {
		return nil, nil
	}
	items, err := r.listResourceGroupItemsWithQuery(ctx, r.db, groupID)
	if err != nil {
		return nil, err
	}
	rewards := make([]ledger.VipRewardItem, 0, len(items))
	for _, item := range items {
		if resourcedomain.NormalizeGroupItemType(item.ItemType) != resourcedomain.GroupItemTypeResource {
			continue
		}
		rewards = append(rewards, ledger.VipRewardItem{
			ResourceID:   item.Resource.ResourceID,
			ResourceCode: item.Resource.ResourceCode,
			ResourceType: item.Resource.ResourceType,
			Name:         item.Resource.Name,
			Quantity:     item.Quantity,
			ExpiresAtMS:  expiresAtMS,
			AssetURL:     item.Resource.AssetURL,
			PreviewURL:   item.Resource.PreviewURL,
			AnimationURL: item.Resource.AnimationURL,
		})
	}
	return rewards, nil
}

func (r *Repository) rewardItemsByGrant(ctx context.Context, tx *sql.Tx, grantID string, expiresAtMS int64) ([]ledger.VipRewardItem, error) {
	if grantID == "" {
		return nil, nil
	}
	rows, err := tx.QueryContext(ctx, `
		SELECT i.resource_id, r.resource_code, r.resource_type, r.name, i.quantity,
		       COALESCE(r.asset_url, ''), COALESCE(r.preview_url, ''), COALESCE(r.animation_url, '')
		FROM resource_grant_items i
		JOIN resources r ON r.app_code = i.app_code AND r.resource_id = i.resource_id
		WHERE i.app_code = ? AND i.grant_id = ? AND i.result_type = ?
		ORDER BY i.grant_item_id ASC`,
		appcode.FromContext(ctx), grantID, resourcedomain.ResultEntitlement,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := make([]ledger.VipRewardItem, 0)
	for rows.Next() {
		var item ledger.VipRewardItem
		if err := rows.Scan(
			&item.ResourceID,
			&item.ResourceCode,
			&item.ResourceType,
			&item.Name,
			&item.Quantity,
			&item.AssetURL,
			&item.PreviewURL,
			&item.AnimationURL,
		); err != nil {
			return nil, err
		}
		item.ExpiresAtMS = expiresAtMS
		items = append(items, item)
	}
	return items, rows.Err()
}
