package main

import (
	"context"
	"database/sql"
	"flag"
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	walletv1 "hyapp.local/api/proto/wallet/v1"
)

const registrationLevelBadgeReason = "growth_level_registration_badge"

func main() {
	var appCode string
	var activityDSN string
	var userDSN string
	var walletAddr string
	var dryRun bool
	var onlyUserID int64
	flag.StringVar(&appCode, "app_code", "lalu", "app_code to backfill")
	flag.StringVar(&activityDSN, "activity_dsn", "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_activity?parseTime=true&charset=utf8mb4&loc=UTC", "activity mysql dsn")
	flag.StringVar(&userDSN, "user_dsn", "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_user?parseTime=true&charset=utf8mb4&loc=UTC", "user mysql dsn")
	flag.StringVar(&walletAddr, "wallet_addr", "127.0.0.1:13004", "wallet-service grpc address")
	flag.BoolVar(&dryRun, "dry_run", true, "print planned grants without calling wallet-service")
	flag.Int64Var(&onlyUserID, "user_id", 0, "optional single user_id")
	flag.Parse()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
	defer cancel()

	activityDB, err := sql.Open("mysql", activityDSN)
	if err != nil {
		log.Fatalf("open activity db: %v", err)
	}
	defer activityDB.Close()
	userDB, err := sql.Open("mysql", userDSN)
	if err != nil {
		log.Fatalf("open user db: %v", err)
	}
	defer userDB.Close()

	badges, err := levelOneBadgeResources(ctx, activityDB, appCode)
	if err != nil {
		log.Fatalf("query level badges: %v", err)
	}
	users, err := targetUsers(ctx, userDB, appCode, onlyUserID)
	if err != nil {
		log.Fatalf("query users: %v", err)
	}

	tracks := []string{"wealth", "game", "charm"}
	for _, track := range tracks {
		if badges[track] <= 0 {
			log.Printf("skip track=%s: level 1 long badge is not configured", track)
		}
	}
	if dryRun {
		log.Printf("dry_run=true app_code=%s users=%d badges=%v", appCode, len(users), badges)
		return
	}

	conn, err := grpc.NewClient(walletAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("dial wallet-service: %v", err)
	}
	defer conn.Close()
	wallet := walletv1.NewWalletServiceClient(conn)

	total := 0
	for _, userID := range users {
		for _, track := range tracks {
			resourceID := badges[track]
			if resourceID <= 0 {
				continue
			}
			// wallet-service 用 command_id 做完整请求幂等；同一个用户同一轨道重复执行只会返回首次发放结果。
			commandID := fmt.Sprintf("registration_level_badges:%s:%d:%s", appCode, userID, track)
			_, err := wallet.GrantResource(ctx, &walletv1.GrantResourceRequest{
				CommandId:      commandID,
				AppCode:        appCode,
				TargetUserId:   userID,
				ResourceId:     resourceID,
				Quantity:       1,
				DurationMs:     0,
				Reason:         registrationLevelBadgeReason,
				OperatorUserId: userID,
				GrantSource:    "growth_level",
			})
			if err != nil {
				log.Fatalf("grant failed user_id=%d track=%s resource_id=%d: %v", userID, track, resourceID, err)
			}
			total++
		}
	}
	log.Printf("done app_code=%s users=%d grant_calls=%d", appCode, len(users), total)
}

func levelOneBadgeResources(ctx context.Context, db *sql.DB, appCode string) (map[string]int64, error) {
	rows, err := db.QueryContext(ctx, `
		SELECT track,
		       COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(display_config_json, '$.long_badge_resource_id')) AS SIGNED), 0) AS resource_id
		FROM growth_level_rules
		WHERE app_code = ? AND status = 'active' AND level = 1
		ORDER BY track ASC`, appCode)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	result := map[string]int64{}
	for rows.Next() {
		var track string
		var resourceID int64
		if err := rows.Scan(&track, &resourceID); err != nil {
			return nil, err
		}
		track = strings.ToLower(strings.TrimSpace(track))
		if resourceID > 0 {
			result[track] = resourceID
		}
	}
	return result, rows.Err()
}

func targetUsers(ctx context.Context, db *sql.DB, appCode string, onlyUserID int64) ([]int64, error) {
	if onlyUserID > 0 {
		return []int64{onlyUserID}, nil
	}
	rows, err := db.QueryContext(ctx, `
		SELECT user_id
		FROM users
		WHERE app_code = ? AND status = 'active'
		ORDER BY user_id ASC`, appCode)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	users := make([]int64, 0)
	for rows.Next() {
		var userID int64
		if err := rows.Scan(&userID); err != nil {
			return nil, err
		}
		users = append(users, userID)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	sort.Slice(users, func(i, j int) bool { return users[i] < users[j] })
	return users, nil
}
