package roomnotice

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

	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
	"hyapp/pkg/tencentim"
)

func TestProcessRoomKickNoticesPublishesPrivateIM(t *testing.T) {
	repo := &fakeRepository{
		events: []RoomKickEvent{{
			AppCode:      "lalu",
			EventID:      "evt-room-kick-1",
			EventType:    "RoomUserKicked",
			RoomID:       "room_10001",
			RoomVersion:  18,
			ActorUserID:  10001,
			TargetUserID: 20001,
			OccurredAtMS: 1710000000123,
			CreatedAtMS:  1710000000124,
		}},
	}
	publisher := &fakePublisher{}
	service := New(Config{NodeID: "notice-test"}, repo, publisher)

	processed, err := service.ProcessRoomKickNotices(context.Background(), RoomNoticeWorkerOptions{
		WorkerID:       "room-notice-test",
		BatchSize:      10,
		LockTTL:        time.Second,
		PublishTimeout: time.Second,
		MaxRetryCount:  3,
		InitialBackoff: time.Second,
		MaxBackoff:     time.Second,
	})
	if err != nil {
		t.Fatalf("ProcessRoomKickNotices failed: %v", err)
	}
	if processed != 1 {
		t.Fatalf("processed=%d, want 1", processed)
	}
	if len(publisher.messages) != 1 {
		t.Fatalf("published messages=%d, want 1", len(publisher.messages))
	}
	message := publisher.messages[0]
	if message.ToAccount != "20001" || message.EventID != "evt-room-kick-1" || message.Ext != "room_system_message" || message.Desc != "room_user_kicked" {
		t.Fatalf("unexpected message: %+v", message)
	}
	if repo.deliveredEventID != "evt-room-kick-1" {
		t.Fatalf("delivered event id=%q", repo.deliveredEventID)
	}

	var payload map[string]any
	if err := json.Unmarshal(message.PayloadJSON, &payload); err != nil {
		t.Fatalf("decode payload: %v", err)
	}
	if payload["event_type"] != "room_user_kicked" || payload["room_id"] != "room_10001" {
		t.Fatalf("unexpected payload: %+v", payload)
	}
	if payload["target_user_id"] != "20001" || payload["actor_user_id"] != "10001" {
		t.Fatalf("unexpected user payload: %+v", payload)
	}
}

func TestProcessRoomKickNoticesMarksRetryableOnPublishError(t *testing.T) {
	repo := &fakeRepository{
		events: []RoomKickEvent{{
			AppCode:      "lalu",
			EventID:      "evt-room-kick-2",
			EventType:    "RoomUserKicked",
			RoomID:       "room_10001",
			ActorUserID:  10001,
			TargetUserID: 20001,
			RetryCount:   1,
		}},
	}
	service := New(Config{NodeID: "notice-test"}, repo, &fakePublisher{err: errors.New("im timeout")})

	processed, err := service.ProcessRoomKickNotices(context.Background(), RoomNoticeWorkerOptions{
		WorkerID:       "room-notice-test",
		BatchSize:      10,
		LockTTL:        time.Second,
		PublishTimeout: time.Second,
		MaxRetryCount:  3,
		InitialBackoff: time.Second,
		MaxBackoff:     time.Second,
	})
	if err != nil {
		t.Fatalf("ProcessRoomKickNotices failed: %v", err)
	}
	if processed != 1 {
		t.Fatalf("processed=%d, want 1", processed)
	}
	if repo.retryEventID != "evt-room-kick-2" || repo.retryCount != 2 {
		t.Fatalf("retry marker event=%q count=%d", repo.retryEventID, repo.retryCount)
	}
	if repo.failedEventID != "" {
		t.Fatalf("event should be retryable, got failed=%q", repo.failedEventID)
	}
}

type fakeRepository struct {
	events           []RoomKickEvent
	deliveredEventID string
	retryEventID     string
	failedEventID    string
	retryCount       int
}

func (r *fakeRepository) ClaimRoomKickEvents(context.Context, string, int, time.Duration) ([]RoomKickEvent, error) {
	return r.events, nil
}

func (r *fakeRepository) ClaimRoomKickEnvelope(context.Context, string, *roomeventsv1.EventEnvelope, time.Duration) (RoomKickEvent, bool, error) {
	if len(r.events) == 0 {
		return RoomKickEvent{}, false, nil
	}
	return r.events[0], true, nil
}

func (r *fakeRepository) MarkRoomKickDelivered(_ context.Context, event RoomKickEvent, _ []byte, _ int64) error {
	r.deliveredEventID = event.EventID
	return nil
}

func (r *fakeRepository) MarkRoomKickRetryable(_ context.Context, event RoomKickEvent, retryCount int, _ int64, _ string, _ int64) error {
	r.retryEventID = event.EventID
	r.retryCount = retryCount
	return nil
}

func (r *fakeRepository) MarkRoomKickFailed(_ context.Context, event RoomKickEvent, retryCount int, _ string, _ int64) error {
	r.failedEventID = event.EventID
	r.retryCount = retryCount
	return nil
}

type fakePublisher struct {
	messages []tencentim.CustomUserMessage
	err      error
}

func (p *fakePublisher) PublishUserCustomMessage(_ context.Context, message tencentim.CustomUserMessage) error {
	if p.err != nil {
		return p.err
	}
	p.messages = append(p.messages, message)
	return nil
}
