package walletnotice

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

	_ "github.com/go-sql-driver/mysql"
	"hyapp/pkg/appcode"
)

func TestMySQLRepositoryProcessesRealWalletOutbox(t *testing.T) {
	dsn := strings.TrimSpace(os.Getenv("NOTICE_REAL_MYSQL_DSN"))
	if dsn == "" {
		t.Skip("set NOTICE_REAL_MYSQL_DSN to run real MySQL notice validation")
	}
	walletDatabase := strings.TrimSpace(os.Getenv("NOTICE_REAL_WALLET_DATABASE"))
	if walletDatabase == "" {
		walletDatabase = "hyapp_wallet"
	}

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		t.Fatalf("open mysql: %v", err)
	}
	t.Cleanup(func() { _ = db.Close() })
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := db.PingContext(ctx); err != nil {
		t.Fatalf("ping mysql: %v", err)
	}

	repository, err := NewMySQLRepository(db, walletDatabase)
	if err != nil {
		t.Fatalf("NewMySQLRepository: %v", err)
	}
	insertedAtMS := int64(0)
	suffix := fmt.Sprintf("%d", time.Now().UnixNano())
	appCode := "notice_real"
	userID := int64(990000000)
	eventID := "notice_real_evt_" + suffix
	transactionID := "notice_real_tx_" + suffix
	commandID := "notice_real_cmd_" + suffix
	payload := map[string]any{
		"transaction_id":  transactionID,
		"command_id":      commandID,
		"user_id":         userID,
		"asset_type":      "COIN",
		"available_delta": int64(188),
		"frozen_delta":    int64(0),
		"available_after": int64(99887766),
		"frozen_after":    int64(0),
		"balance_version": int64(66),
		"metadata": map[string]any{
			"validation": "notice-service-real-mysql",
		},
		"created_at_ms": insertedAtMS,
	}
	payloadJSON, err := json.Marshal(payload)
	if err != nil {
		t.Fatalf("marshal payload: %v", err)
	}
	if _, err := db.ExecContext(ctx, fmt.Sprintf(`
		INSERT INTO %s.wallet_outbox (
		  app_code, event_id, event_type, transaction_id, command_id, user_id, asset_type,
		  available_delta, frozen_delta, payload, status, retry_count, last_error, created_at_ms, updated_at_ms
		) VALUES (?, ?, 'WalletBalanceChanged', ?, ?, ?, 'COIN', 188, 0, ?, 'pending', 0, '', ?, ?)`,
		quoteDB(walletDatabase),
	), appCode, eventID, transactionID, commandID, userID, string(payloadJSON), insertedAtMS, insertedAtMS); err != nil {
		t.Fatalf("insert wallet_outbox: %v", err)
	}
	if !keepRealRows() {
		t.Cleanup(func() {
			_, _ = db.ExecContext(context.Background(), `DELETE FROM notice_delivery_events WHERE app_code = ? AND source_event_id = ?`, appCode, eventID)
			_, _ = db.ExecContext(context.Background(), fmt.Sprintf(`DELETE FROM %s.wallet_outbox WHERE app_code = ? AND event_id = ?`, quoteDB(walletDatabase)), appCode, eventID)
		})
	}

	publisher := &fakePublisher{}
	service := New(Config{NodeID: "notice-real-test"}, repository, publisher)
	processed, err := service.ProcessWalletBalanceNotices(appcode.WithContext(ctx, appCode), WalletNoticeWorkerOptions{
		WorkerID:       "notice-real-test-worker",
		BatchSize:      1,
		LockTTL:        30 * time.Second,
		PublishTimeout: time.Second,
		MaxRetryCount:  3,
	})
	if err != nil {
		t.Fatalf("ProcessWalletBalanceNotices: %v", err)
	}
	if processed != 1 || len(publisher.messages) != 1 {
		t.Fatalf("unexpected process result: processed=%d messages=%d", processed, len(publisher.messages))
	}
	var noticeStatus string
	var deliveredAtMS int64
	var storedPayload string
	if err := db.QueryRowContext(ctx, `
		SELECT status, delivered_at_ms, COALESCE(CAST(payload_json AS CHAR), '{}')
		FROM notice_delivery_events
		WHERE source_name = 'wallet_outbox' AND app_code = ? AND source_event_id = ? AND channel = 'tencent_im_c2c'`,
		appCode, eventID,
	).Scan(&noticeStatus, &deliveredAtMS, &storedPayload); err != nil {
		t.Fatalf("query notice_delivery_events: %v", err)
	}
	if noticeStatus != "delivered" || deliveredAtMS <= 0 {
		t.Fatalf("unexpected delivery marker: status=%s delivered_at_ms=%d payload=%s", noticeStatus, deliveredAtMS, storedPayload)
	}
	var noticePayload map[string]any
	if err := json.Unmarshal([]byte(storedPayload), &noticePayload); err != nil {
		t.Fatalf("decode stored notice payload: %v", err)
	}
	if noticePayload["event_id"] != eventID || noticePayload["asset_type"] != "COIN" || noticePayload["balance_version"].(float64) != 66 {
		t.Fatalf("unexpected stored notice payload: %+v", noticePayload)
	}
	t.Logf("real notice validation event_id=%s status=%s delivered_at_ms=%d", eventID, noticeStatus, deliveredAtMS)
}

func TestMySQLRepositoryProcessesRealVipActivatedOutbox(t *testing.T) {
	dsn := strings.TrimSpace(os.Getenv("NOTICE_REAL_MYSQL_DSN"))
	if dsn == "" {
		t.Skip("set NOTICE_REAL_MYSQL_DSN to run real MySQL notice validation")
	}
	walletDatabase := strings.TrimSpace(os.Getenv("NOTICE_REAL_WALLET_DATABASE"))
	if walletDatabase == "" {
		walletDatabase = "hyapp_wallet"
	}

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		t.Fatalf("open mysql: %v", err)
	}
	t.Cleanup(func() { _ = db.Close() })
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := db.PingContext(ctx); err != nil {
		t.Fatalf("ping mysql: %v", err)
	}

	repository, err := NewMySQLRepository(db, walletDatabase)
	if err != nil {
		t.Fatalf("NewMySQLRepository: %v", err)
	}
	insertedAtMS := int64(0)
	suffix := fmt.Sprintf("%d", time.Now().UnixNano())
	appCode := "notice_real"
	userID := int64(990000001)
	eventID := "notice_real_vip_evt_" + suffix
	transactionID := "notice_real_vip_tx_" + suffix
	commandID := "notice_real_vip_cmd_" + suffix
	payload := map[string]any{
		"event_type":     "VipActivated",
		"transaction_id": transactionID,
		"command_id":     commandID,
		"user_id":        userID,
		"asset_type":     "VIP",
		"source":         "admin_grant",
		"action":         "admin_grant",
		"previous_level": int32(0),
		"level":          int32(3),
		"vip_name":       "VIP3",
		"created_at_ms":  insertedAtMS,
	}
	payloadJSON, err := json.Marshal(payload)
	if err != nil {
		t.Fatalf("marshal payload: %v", err)
	}
	if _, err := db.ExecContext(ctx, fmt.Sprintf(`
		INSERT INTO %s.wallet_outbox (
		  app_code, event_id, event_type, transaction_id, command_id, user_id, asset_type,
		  available_delta, frozen_delta, payload, status, retry_count, last_error, created_at_ms, updated_at_ms
		) VALUES (?, ?, 'VipActivated', ?, ?, ?, 'VIP', 0, 0, ?, 'pending', 0, '', ?, ?)`,
		quoteDB(walletDatabase),
	), appCode, eventID, transactionID, commandID, userID, string(payloadJSON), insertedAtMS, insertedAtMS); err != nil {
		t.Fatalf("insert vip wallet_outbox: %v", err)
	}
	if !keepRealRows() {
		t.Cleanup(func() {
			_, _ = db.ExecContext(context.Background(), `DELETE FROM notice_delivery_events WHERE app_code = ? AND source_event_id = ?`, appCode, eventID)
			_, _ = db.ExecContext(context.Background(), fmt.Sprintf(`DELETE FROM %s.wallet_outbox WHERE app_code = ? AND event_id = ?`, quoteDB(walletDatabase)), appCode, eventID)
		})
	}

	publisher := &fakePublisher{}
	service := New(Config{NodeID: "notice-real-test"}, repository, publisher)
	processed, err := service.ProcessWalletBalanceNotices(appcode.WithContext(ctx, appCode), WalletNoticeWorkerOptions{
		WorkerID:       "notice-real-test-worker",
		BatchSize:      1,
		LockTTL:        30 * time.Second,
		PublishTimeout: time.Second,
		MaxRetryCount:  3,
	})
	if err != nil {
		t.Fatalf("ProcessWalletBalanceNotices vip: %v", err)
	}
	if processed != 1 || len(publisher.messages) != 1 {
		t.Fatalf("unexpected vip process result: processed=%d messages=%d", processed, len(publisher.messages))
	}
	message := publisher.messages[0]
	if message.ToAccount != fmt.Sprintf("%d", userID) || message.EventID != eventID || message.Desc != "VipActivated" || message.Ext != "vip_notice" {
		t.Fatalf("unexpected vip c2c message: %+v", message)
	}
	var noticeStatus string
	var deliveredAtMS int64
	var storedPayload string
	if err := db.QueryRowContext(ctx, `
		SELECT status, delivered_at_ms, COALESCE(CAST(payload_json AS CHAR), '{}')
		FROM notice_delivery_events
		WHERE source_name = 'wallet_outbox' AND app_code = ? AND source_event_id = ? AND channel = 'tencent_im_c2c'`,
		appCode, eventID,
	).Scan(&noticeStatus, &deliveredAtMS, &storedPayload); err != nil {
		t.Fatalf("query vip notice_delivery_events: %v", err)
	}
	var noticePayload map[string]any
	if err := json.Unmarshal([]byte(storedPayload), &noticePayload); err != nil {
		t.Fatalf("decode stored vip notice payload: %v", err)
	}
	if noticeStatus != "delivered" || deliveredAtMS <= 0 || noticePayload["event_type"] != "VipActivated" || noticePayload["asset_type"] != "VIP" || noticePayload["level"].(float64) != 3 {
		t.Fatalf("unexpected vip delivery marker: status=%s delivered_at_ms=%d payload=%+v", noticeStatus, deliveredAtMS, noticePayload)
	}
	t.Logf("real vip notice validation event_id=%s ext=%s status=%s", eventID, message.Ext, noticeStatus)
}

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

func quoteDB(value string) string {
	return "`" + strings.ReplaceAll(value, "`", "``") + "`"
}
