package wallet_test

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	_ "github.com/go-sql-driver/mysql"

	"hyapp/services/wallet-service/internal/domain/ledger"
	walletservice "hyapp/services/wallet-service/internal/service/wallet"
	mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql"
)

func TestRealMySQLGrantVipWritesActivationOutbox(t *testing.T) {
	dsn := strings.TrimSpace(os.Getenv("WALLET_REAL_MYSQL_DSN"))
	if dsn == "" {
		t.Skip("set WALLET_REAL_MYSQL_DSN to run real MySQL VIP grant validation")
	}

	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		t.Fatalf("open mysql: %v", err)
	}
	t.Cleanup(func() { _ = db.Close() })
	if err := db.PingContext(ctx); err != nil {
		t.Fatalf("ping mysql: %v", err)
	}

	repository, err := mysqlstorage.Open(ctx, dsn)
	if err != nil {
		t.Fatalf("open wallet repository: %v", err)
	}
	defer repository.Close()

	level, vipName := realActiveVIPLevel(t, ctx, db)
	suffix := time.Now().UnixNano()
	userID := int64(990100000 + suffix%100000)
	commandID := fmt.Sprintf("real_vip_grant_%d", suffix)
	operatorUserID := int64(990000001)
	if !keepWalletRealRows() {
		t.Cleanup(func() {
			cleanupRealVIPGrantRows(context.Background(), t, db, userID, commandID)
		})
	}

	service := walletservice.New(repository)
	receipt, err := service.GrantVip(ctx, ledger.GrantVipCommand{
		CommandID:      commandID,
		AppCode:        "lalu",
		TargetUserID:   userID,
		Level:          level,
		GrantSource:    ledger.VipGrantSourceAdmin,
		OperatorUserID: operatorUserID,
		Reason:         "real mysql vip grant validation",
	})
	if err != nil {
		t.Fatalf("GrantVip real mysql failed: %v", err)
	}
	if receipt.TransactionID == "" || receipt.Vip.UserID != userID || receipt.Vip.Level != level || !receipt.Vip.Active {
		t.Fatalf("unexpected GrantVip receipt: %+v", receipt)
	}

	again, err := service.GrantVip(ctx, ledger.GrantVipCommand{
		CommandID:      commandID,
		AppCode:        "lalu",
		TargetUserID:   userID,
		Level:          level,
		GrantSource:    ledger.VipGrantSourceAdmin,
		OperatorUserID: operatorUserID,
		Reason:         "real mysql vip grant validation",
	})
	if err != nil {
		t.Fatalf("GrantVip real mysql retry failed: %v", err)
	}
	if again.TransactionID != receipt.TransactionID || again.Vip.Level != receipt.Vip.Level {
		t.Fatalf("GrantVip idempotency mismatch: first=%+v again=%+v", receipt, again)
	}

	var membershipLevel int32
	var membershipName string
	if err := db.QueryRowContext(ctx, `
		SELECT level, name
		FROM user_vip_memberships
		WHERE app_code = 'lalu' AND user_id = ? AND status = 'active'`,
		userID,
	).Scan(&membershipLevel, &membershipName); err != nil {
		t.Fatalf("query real vip membership: %v", err)
	}
	if membershipLevel != level || membershipName != vipName {
		t.Fatalf("unexpected real vip membership: level=%d name=%s", membershipLevel, membershipName)
	}

	var outboxStatus string
	var assetType string
	var payloadRaw string
	if err := db.QueryRowContext(ctx, `
		SELECT status, asset_type, COALESCE(CAST(payload AS CHAR), '{}')
		FROM wallet_outbox
		WHERE app_code = 'lalu' AND transaction_id = ? AND event_type = 'VipActivated'`,
		receipt.TransactionID,
	).Scan(&outboxStatus, &assetType, &payloadRaw); err != nil {
		t.Fatalf("query real vip outbox: %v", err)
	}
	var payload map[string]any
	if err := json.Unmarshal([]byte(payloadRaw), &payload); err != nil {
		t.Fatalf("decode real vip outbox payload: %v", err)
	}
	if outboxStatus != "pending" || assetType != "VIP" || payload["event_type"] != "VipActivated" || payload["source"] != ledger.VipGrantSourceAdmin || int32(payload["level"].(float64)) != level {
		t.Fatalf("unexpected real vip outbox: status=%s asset=%s payload=%+v", outboxStatus, assetType, payload)
	}
	t.Logf("real wallet VIP grant user_id=%d level=%d transaction_id=%s outbox_status=%s", userID, level, receipt.TransactionID, outboxStatus)
}

func realActiveVIPLevel(t *testing.T, ctx context.Context, db *sql.DB) (int32, string) {
	t.Helper()

	var level int32
	var name string
	err := db.QueryRowContext(ctx, `
		SELECT level, name
		FROM vip_levels
		WHERE app_code = 'lalu' AND status = 'active'
		ORDER BY level ASC
		LIMIT 1`,
	).Scan(&level, &name)
	if err == sql.ErrNoRows {
		t.Skip("real hyapp_wallet.vip_levels has no active VIP level")
	}
	if err != nil {
		t.Fatalf("query active vip level: %v", err)
	}
	return level, name
}

func cleanupRealVIPGrantRows(ctx context.Context, t testing.TB, db *sql.DB, userID int64, commandID string) {
	t.Helper()

	rewardCommandID := "vip_reward:" + commandID
	_, _ = db.ExecContext(ctx, `DELETE FROM wallet_outbox WHERE app_code = 'lalu' AND (command_id = ? OR command_id = ? OR command_id LIKE ?)`, commandID, rewardCommandID, "resource_group_grant:"+rewardCommandID+"%")
	_, _ = db.ExecContext(ctx, `DELETE FROM wallet_entries WHERE app_code = 'lalu' AND user_id = ? AND transaction_id IN (SELECT transaction_id FROM wallet_transactions WHERE app_code = 'lalu' AND (command_id = ? OR command_id LIKE ?))`, userID, commandID, "resource_group_grant:"+rewardCommandID+"%")
	_, _ = db.ExecContext(ctx, `DELETE FROM wallet_transactions WHERE app_code = 'lalu' AND (command_id = ? OR command_id = ? OR command_id LIKE ?)`, commandID, rewardCommandID, "resource_group_grant:"+rewardCommandID+"%")
	_, _ = db.ExecContext(ctx, `DELETE FROM user_resource_equipment WHERE app_code = 'lalu' AND user_id = ?`, userID)
	_, _ = db.ExecContext(ctx, `DELETE FROM user_resource_entitlements WHERE app_code = 'lalu' AND user_id = ?`, userID)
	_, _ = db.ExecContext(ctx, `
		DELETE rgi
		FROM resource_grant_items rgi
		JOIN resource_grants rg ON rg.app_code = rgi.app_code AND rg.grant_id = rgi.grant_id
		WHERE rg.app_code = 'lalu' AND rg.target_user_id = ? AND rg.command_id = ?`,
		userID, rewardCommandID,
	)
	_, _ = db.ExecContext(ctx, `DELETE FROM resource_grants WHERE app_code = 'lalu' AND target_user_id = ? AND command_id = ?`, userID, rewardCommandID)
	_, _ = db.ExecContext(ctx, `DELETE FROM user_vip_history WHERE app_code = 'lalu' AND user_id = ?`, userID)
	_, _ = db.ExecContext(ctx, `DELETE FROM user_vip_memberships WHERE app_code = 'lalu' AND user_id = ?`, userID)
}

func keepWalletRealRows() bool {
	value := strings.ToLower(strings.TrimSpace(os.Getenv("WALLET_REAL_MYSQL_KEEP_ROWS")))
	return value == "1" || value == "true" || value == "yes"
}
