package integration

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"testing"
	"time"

	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
	"hyapp/pkg/tencentim"
	"hyapp/services/room-service/internal/room/outbox"
)

func TestRoomEventFromEnvelopeSkipsNonIMEvents(t *testing.T) {
	record, err := outbox.Build("room-noop", "RoomCreated", 1, time.Now(), &roomeventsv1.RoomCreated{
		OwnerUserId: 1,
		SeatCount:   4,
		Mode:        "social",
	})
	if err != nil {
		t.Fatalf("Build RoomCreated envelope failed: %v", err)
	}

	event, publish, err := roomEventFromEnvelope(record.Envelope)
	if err != nil {
		t.Fatalf("RoomCreated should be a no-op success: %v", err)
	}
	if publish {
		t.Fatalf("RoomCreated must not publish Tencent IM message: %+v", event)
	}
}

func TestRoomUserJoinedCarriesEntryVehicleSnapshot(t *testing.T) {
	record, err := outbox.Build("room-vehicle", "RoomUserJoined", 9, time.Now(), &roomeventsv1.RoomUserJoined{
		UserId:          42,
		Role:            "audience",
		VisibleRegionId: 86,
		EntryVehicle: &roomeventsv1.RoomEntryVehicleSnapshot{
			ResourceId:    7001,
			ResourceCode:  "vehicle_gold",
			Name:          "Gold Vehicle",
			AssetUrl:      "https://cdn.example.com/vehicle.png",
			PreviewUrl:    "https://cdn.example.com/vehicle-preview.png",
			AnimationUrl:  "https://cdn.example.com/vehicle.svga",
			MetadataJson:  `{"speed":"fast"}`,
			EntitlementId: "ent-vehicle",
			ExpiresAtMs:   1999999999999,
		},
	})
	if err != nil {
		t.Fatalf("Build RoomUserJoined envelope failed: %v", err)
	}

	event, publish, err := roomEventFromEnvelope(record.Envelope)
	if err != nil {
		t.Fatalf("RoomUserJoined should decode: %v", err)
	}
	if !publish {
		t.Fatal("RoomUserJoined must publish Tencent IM message")
	}
	if event.EntryVehicle == nil || event.EntryVehicle.ResourceID != 7001 || event.EntryVehicle.AnimationURL != "https://cdn.example.com/vehicle.svga" {
		t.Fatalf("entry vehicle snapshot mismatch: %+v", event.EntryVehicle)
	}
}

func TestRoomBackgroundChangedPublishesDedicatedIMEvent(t *testing.T) {
	record, err := outbox.Build("room-bg", "RoomBackgroundChanged", 7, time.Now(), &roomeventsv1.RoomBackgroundChanged{
		ActorUserId:       42,
		BackgroundId:      101,
		RoomBackgroundUrl: "https://cdn.example.com/bg.png",
	})
	if err != nil {
		t.Fatalf("Build RoomBackgroundChanged envelope failed: %v", err)
	}

	event, publish, err := roomEventFromEnvelope(record.Envelope)
	if err != nil {
		t.Fatalf("RoomBackgroundChanged should decode: %v", err)
	}
	if !publish {
		t.Fatal("RoomBackgroundChanged must publish Tencent IM message")
	}
	if event.EventType != "room_background_changed" || event.Attributes["room_background_url"] != "https://cdn.example.com/bg.png" || event.Attributes["background_id"] != "101" {
		t.Fatalf("background IM event mismatch: %+v", event)
	}
}

func TestRoomGiftSentCarriesTargetGiftValueInIMAttributes(t *testing.T) {
	record, err := outbox.Build("room-gift-value", "RoomGiftSent", 8, time.Now(), &roomeventsv1.RoomGiftSent{
		SenderUserId:    1001,
		TargetUserId:    1002,
		GiftId:          "gift_rose",
		GiftCount:       1,
		GiftValue:       117,
		TargetGiftValue: 367,
	})
	if err != nil {
		t.Fatalf("Build RoomGiftSent envelope failed: %v", err)
	}

	event, publish, err := roomEventFromEnvelope(record.Envelope)
	if err != nil {
		t.Fatalf("RoomGiftSent should decode: %v", err)
	}
	if !publish {
		t.Fatal("RoomGiftSent must publish Tencent IM message")
	}
	if event.EventType != "room_gift_sent" || event.GiftValue != 117 || event.Attributes["target_gift_value"] != "367" {
		t.Fatalf("gift IM event target gift value mismatch: %+v", event)
	}
}

func TestRoomMicChangedCarriesTargetGiftValueInIMAttributes(t *testing.T) {
	record, err := outbox.Build("room-mic-value", "RoomMicChanged", 9, time.Now(), &roomeventsv1.RoomMicChanged{
		ActorUserId:     1001,
		TargetUserId:    1002,
		FromSeat:        2,
		ToSeat:          5,
		Action:          "change",
		TargetGiftValue: 367,
	})
	if err != nil {
		t.Fatalf("Build RoomMicChanged envelope failed: %v", err)
	}

	event, publish, err := roomEventFromEnvelope(record.Envelope)
	if err != nil {
		t.Fatalf("RoomMicChanged should decode: %v", err)
	}
	if !publish {
		t.Fatal("RoomMicChanged must publish Tencent IM message")
	}
	if event.EventType != "room_mic_changed" || event.SeatNo != 5 || event.Attributes["target_gift_value"] != "367" {
		t.Fatalf("mic IM event target gift value mismatch: %+v", event)
	}
}

func TestRoomPasswordChangedSkipsRoomGroupIM(t *testing.T) {
	record, err := outbox.Build("room-lock", "RoomPasswordChanged", 8, time.Now(), &roomeventsv1.RoomPasswordChanged{
		ActorUserId:     42,
		Locked:          true,
		VisibleRegionId: 86,
	})
	if err != nil {
		t.Fatalf("Build RoomPasswordChanged envelope failed: %v", err)
	}

	event, publish, err := roomEventFromEnvelope(record.Envelope)
	if err != nil {
		t.Fatalf("RoomPasswordChanged should decode: %v", err)
	}
	if publish {
		t.Fatalf("RoomPasswordChanged must use activity region broadcast, not room group IM: %+v", event)
	}
}

func TestTencentIMPublisherRemovesGroupMemberBeforeKickMessage(t *testing.T) {
	paths := make([]string, 0, 2)
	bodies := make([]map[string]any, 0, 2)
	client, err := tencentim.NewRESTClient(tencentim.RESTConfig{
		SDKAppID:        1400000000,
		SecretKey:       "secret",
		AdminIdentifier: "administrator",
		AdminUserSigTTL: time.Hour,
		HTTPClient: &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
			paths = append(paths, request.URL.Path)
			var body map[string]any
			if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
				t.Fatalf("decode request body failed: %v", err)
			}
			bodies = append(bodies, body)
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(bytes.NewReader([]byte(`{"ActionStatus":"OK","ErrorCode":0,"ErrorInfo":""}`))),
				Header:     make(http.Header),
			}, nil
		})},
	})
	if err != nil {
		t.Fatalf("NewRESTClient failed: %v", err)
	}

	record, err := outbox.Build("room-kick", "RoomUserKicked", 3, time.Now(), &roomeventsv1.RoomUserKicked{
		ActorUserId:  1,
		TargetUserId: 2,
	})
	if err != nil {
		t.Fatalf("Build RoomUserKicked envelope failed: %v", err)
	}
	publisher := NewTencentIMPublisher(client)
	if err := publisher.PublishOutboxEvent(context.Background(), record.Envelope); err != nil {
		t.Fatalf("PublishOutboxEvent failed: %v", err)
	}

	if len(paths) != 2 || paths[0] != "/v4/group_open_http_svc/delete_group_member" || paths[1] != "/v4/group_open_http_svc/send_group_msg" {
		t.Fatalf("kick outbox should delete member before system message, paths=%+v", paths)
	}
	if bodies[0]["GroupId"] != "room-kick" || bodies[1]["GroupId"] != "room-kick" {
		t.Fatalf("kick outbox must use original room group id, bodies=%+v", bodies)
	}
}

func TestTencentIMPublisherEnsuresRoomGroupAndRetriesWhenGroupMissing(t *testing.T) {
	paths := make([]string, 0, 3)
	bodies := make([]map[string]any, 0, 3)
	client, err := tencentim.NewRESTClient(tencentim.RESTConfig{
		SDKAppID:        1400000000,
		SecretKey:       "secret",
		AdminIdentifier: "administrator",
		AdminUserSigTTL: time.Hour,
		HTTPClient: &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
			paths = append(paths, request.URL.Path)
			var body map[string]any
			if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
				t.Fatalf("decode request body failed: %v", err)
			}
			bodies = append(bodies, body)
			if len(paths) == 1 {
				return &http.Response{
					StatusCode: http.StatusOK,
					Body:       io.NopCloser(bytes.NewReader([]byte(`{"ActionStatus":"FAIL","ErrorCode":10015,"ErrorInfo":"bytes_group_id invalid"}`))),
					Header:     make(http.Header),
				}, nil
			}
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(bytes.NewReader([]byte(`{"ActionStatus":"OK","ErrorCode":0,"ErrorInfo":""}`))),
				Header:     make(http.Header),
			}, nil
		})},
	})
	if err != nil {
		t.Fatalf("NewRESTClient failed: %v", err)
	}

	publisher := NewTencentIMPublisher(client)
	err = publisher.PublishRoomEvent(context.Background(), tencentim.RoomEvent{
		EventID:   "evt-retry",
		RoomID:    "lalu_2963d975-316e-444d-ad5c-f82fc36c28ca",
		EventType: "room_mic_changed",
	})
	if err != nil {
		t.Fatalf("PublishRoomEvent should retry after ensuring group: %v", err)
	}

	wantPaths := []string{
		"/v4/group_open_http_svc/send_group_msg",
		"/v4/group_open_http_svc/create_group",
		"/v4/group_open_http_svc/send_group_msg",
	}
	if len(paths) != len(wantPaths) {
		t.Fatalf("path count mismatch: paths=%+v bodies=%+v", paths, bodies)
	}
	for index, want := range wantPaths {
		if paths[index] != want {
			t.Fatalf("path[%d] mismatch: got %s want %s", index, paths[index], want)
		}
	}
	for index, body := range bodies {
		if body["GroupId"] != "lalu_2963d975-316e-444d-ad5c-f82fc36c28ca" {
			t.Fatalf("request %d must preserve original room group id: %+v", index, body)
		}
	}
}

type roundTripFunc func(*http.Request) (*http.Response, error)

func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
	return f(request)
}
