package broadcast

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/tencentim"
	broadcastdomain "hyapp/services/activity-service/internal/domain/broadcast"
	"hyapp/services/activity-service/internal/testutil/mysqltest"
)

func TestBroadcastServiceUsesRealMySQLOutbox(t *testing.T) {
	// 这个测试使用生产 initdb 创建隔离 MySQL schema，验证真实 JSON 列、主键幂等、claim 锁和 delivered 更新。
	repository := mysqltest.NewRepository(t)
	publisher := &fakePublisher{}
	service := New(Config{NodeID: "node-real", SuperGiftMinValue: 100}, repository.Repository, publisher, nil)
	service.SetClock(func() time.Time { return time.UnixMilli(1_700_000_123_456) })
	ctx := appcode.WithContext(context.Background(), "lalu")

	envelope := mustGiftEnvelope(t, &roomeventsv1.RoomGiftSent{
		SenderUserId:    42,
		TargetUserId:    43,
		GiftId:          "super_rocket",
		GiftCount:       3,
		GiftValue:       300000,
		VisibleRegionId: 210,
		CommandId:       "cmd-real-send-gift-1",
	})

	created, err := service.HandleRoomEvent(ctx, envelope)
	if err != nil {
		t.Fatalf("HandleRoomEvent with real mysql failed: %v", err)
	}
	if !created.BroadcastCreated || created.BroadcastEventID != "gift_broadcast:room-1001:cmd-real-send-gift-1" {
		t.Fatalf("unexpected first consume result: %+v", created)
	}
	t.Logf("real mysql enqueue: room_id=%s region_id=%d gift_value=%d broadcast_event_id=%s", envelope.GetRoomId(), int64(210), int64(300000), created.BroadcastEventID)

	// 同一个 room outbox 事实再次消费必须命中 MySQL 主键幂等，不能产生第二条播报。
	again, err := service.HandleRoomEvent(ctx, envelope)
	if err != nil {
		t.Fatalf("second HandleRoomEvent with real mysql failed: %v", err)
	}
	if again.BroadcastCreated {
		t.Fatalf("duplicate room event should not create another broadcast: %+v", again)
	}

	record, exists, err := repository.Repository.GetBroadcastOutbox(ctx, created.BroadcastEventID)
	if err != nil {
		t.Fatalf("read real mysql outbox failed: %v", err)
	}
	if !exists || record.Status != broadcastdomain.StatusPending || record.GroupID != "hy_lalu_bc_r_v2_210" {
		t.Fatalf("unexpected pending outbox record: exists=%v record=%+v", exists, record)
	}
	t.Logf("real mysql pending record: event_id=%s group_id=%s status=%s", record.EventID, record.GroupID, record.Status)
	assertJSONField(t, record.PayloadJSON, "room_id", "room-1001")
	assertJSONField(t, record.PayloadJSON, "gift_id", "super_rocket")
	assertJSONField(t, record.PayloadJSON, "scope", broadcastdomain.ScopeRegion)

	result, err := service.ProcessPendingBroadcasts(ctx, WorkerOptions{WorkerID: "worker-real", BatchSize: 1, PollInterval: time.Second})
	if err != nil {
		t.Fatalf("ProcessPendingBroadcasts with real mysql failed: %v", err)
	}
	if result.ClaimedCount != 1 || result.SuccessCount != 1 || result.FailureCount != 0 {
		t.Fatalf("unexpected process result: %+v", result)
	}
	// v2 区域群投递成功后过渡期会向 v1 旧群补发一条 _legacy 副本。
	if len(publisher.messages) != 2 {
		t.Fatalf("expected v2 message plus legacy copy, got %d", len(publisher.messages))
	}
	assertPublishedMessage(t, publisher.messages[0], "hy_lalu_bc_r_v2_210", created.BroadcastEventID)
	assertPublishedMessage(t, publisher.messages[1], "hy_lalu_bc_r_210", created.BroadcastEventID+"_legacy")

	delivered, exists, err := repository.Repository.GetBroadcastOutbox(ctx, created.BroadcastEventID)
	if err != nil {
		t.Fatalf("read delivered real mysql outbox failed: %v", err)
	}
	if !exists || delivered.Status != broadcastdomain.StatusDelivered || delivered.LockedBy != "" || delivered.LockedUntilMS != 0 {
		t.Fatalf("unexpected delivered outbox record: exists=%v record=%+v", exists, delivered)
	}
	t.Logf("real mysql delivered record: event_id=%s group_id=%s status=%s", delivered.EventID, delivered.GroupID, delivered.Status)
}

func TestRedPacketCreatedWalletEventUsesRealMySQLOutbox(t *testing.T) {
	repository := mysqltest.NewRepository(t)
	service := New(Config{NodeID: "node-real", SuperGiftMinValue: 100}, repository.Repository, nil, nil)
	service.SetClock(func() time.Time { return time.UnixMilli(1_700_000_456_000) })
	ctx := appcode.WithContext(context.Background(), "lalu")

	// 只有 delayed 且带 room_id/region_id 的红包事实才会产生房间 CREATED 加区域飘屏两条记录。
	event := broadcastdomain.RedPacketCreatedEvent{
		AppCode:       "lalu",
		EventID:       "wallet-red-packet-created-real",
		TransactionID: "wallet-tx-red-packet-real",
		CommandID:     "cmd-red-packet-real",
		PacketID:      "rp-real",
		PacketType:    "delayed",
		RoomID:        "room-310",
		RegionID:      310,
		PayloadJSON:   `{"packet_id":"rp-real","room_id":"room-310","region_id":310}`,
		OpenAtMS:      1_700_000_755_000,
		CreatedAtMS:   1_700_000_455_000,
	}
	if err := service.ConsumeRedPacketCreatedEvent(ctx, event); err != nil {
		t.Fatalf("ConsumeRedPacketCreatedEvent failed: %v", err)
	}
	if err := service.ConsumeRedPacketCreatedEvent(ctx, event); err != nil {
		t.Fatalf("duplicate ConsumeRedPacketCreatedEvent failed: %v", err)
	}

	roomRecord, exists, err := repository.Repository.GetBroadcastOutbox(ctx, event.EventID)
	if err != nil {
		t.Fatalf("read red packet room broadcast outbox failed: %v", err)
	}
	if !exists || roomRecord.BroadcastType != broadcastdomain.TypeRedPacket || roomRecord.GroupID != "room-310" || roomRecord.Status != broadcastdomain.StatusPending {
		t.Fatalf("unexpected red packet room broadcast record: exists=%v record=%+v", exists, roomRecord)
	}

	record, exists, err := repository.Repository.GetBroadcastOutbox(ctx, event.EventID+":region_created")
	if err != nil {
		t.Fatalf("read red packet region broadcast outbox failed: %v", err)
	}
	if !exists || record.BroadcastType != broadcastdomain.TypeRedPacket || record.GroupID != "hy_lalu_bc_r_v2_310" || record.Status != broadcastdomain.StatusPending {
		t.Fatalf("unexpected red packet region broadcast record: exists=%v record=%+v", exists, record)
	}
	assertJSONField(t, record.PayloadJSON, "region_id", float64(310))
}

func assertJSONField(t *testing.T, payloadJSON string, key string, want any) {
	t.Helper()

	var payload map[string]any
	if err := json.Unmarshal([]byte(payloadJSON), &payload); err != nil {
		t.Fatalf("payload is not valid json: %v\npayload=%s", err, payloadJSON)
	}
	if got := payload[key]; got != want {
		t.Fatalf("json field %s mismatch: got=%v want=%v payload=%+v", key, got, want, payload)
	}
}

func assertPublishedMessage(t *testing.T, message tencentim.CustomGroupMessage, groupID string, eventID string) {
	t.Helper()

	if message.GroupID != groupID || message.EventID != eventID || message.Desc != broadcastdomain.TypeSuperGift || message.Ext != "im_broadcast" {
		t.Fatalf("unexpected Tencent custom message: %+v", message)
	}
	assertJSONField(t, string(message.PayloadJSON), "event_id", eventID)
}
