package mysql

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"hyapp/pkg/appcode"
	roomservice "hyapp/services/room-service/internal/room/service"
)

func TestUpdateRoomOwnerVIPUsesOwnerIndexProjection(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	repository := &Repository{db: db}
	expiresAtMS := time.Now().UTC().Add(time.Hour).UnixMilli()
	mock.ExpectExec(`(?s)UPDATE room_list_entries.*SET owner_vip_level = \?, owner_vip_expires_at_ms = \?.*WHERE app_code = \? AND owner_user_id = \?`).
		WithArgs(int32(5), expiresAtMS, appcode.Default, int64(6201)).
		WillReturnResult(sqlmock.NewResult(0, 1))
	rows := sqlmock.NewRows([]string{
		"app_code", "room_id", "room_short_id", "visible_region_id", "owner_country_code", "owner_user_id",
		"title", "cover_url", "mode", "status", "locked", "heat", "online_count", "seat_count",
		"occupied_seat_count", "sort_score", "created_at_ms", "updated_at_ms", "room_border_json", "room_name_style_json",
		"owner_vip_level", "owner_vip_expires_at_ms",
	}).AddRow(
		appcode.Default, "room-owner-vip", "6201", int64(7101), "US", int64(6201),
		"VIP room", "", "voice", "active", false, int64(0), int64(1), int64(8),
		int64(1), int64(99), int64(1700000000000), int64(1700000001000), "", "", int64(5), expiresAtMS,
	)
	mock.ExpectQuery(`(?s)SELECT .*owner_vip_level, owner_vip_expires_at_ms.*FROM room_list_entries.*WHERE app_code = \? AND owner_user_id = \?.*LIMIT 1`).
		WithArgs(appcode.Default, int64(6201)).
		WillReturnRows(rows)

	entry, exists, err := repository.UpdateRoomOwnerVIP(context.Background(), 6201, 5, expiresAtMS)
	if err != nil {
		t.Fatalf("update room owner VIP: %v", err)
	}
	if !exists || entry.OwnerVIPLevel != 5 || entry.OwnerVIPExpiresAtMS != expiresAtMS || !entry.OwnerVIPProjectionKnown {
		t.Fatalf("unexpected owner VIP projection: exists=%v entry=%+v", exists, entry)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestListRoomListEntriesByOwnersScansPinPlaceholders(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	repository := &Repository{db: db}
	rows := sqlmock.NewRows([]string{
		"app_code",
		"room_id",
		"room_short_id",
		"visible_region_id",
		"owner_country_code",
		"owner_user_id",
		"title",
		"cover_url",
		"mode",
		"status",
		"locked",
		"heat",
		"online_count",
		"seat_count",
		"occupied_seat_count",
		"sort_score",
		"created_at_ms",
		"updated_at_ms",
		"is_pinned",
		"pin_list_rank",
		"pin_weight",
		"pinned_until_ms",
	}).AddRow(
		appcode.Default,
		"room-agency-owner",
		"10001",
		int64(7101),
		"US",
		int64(6201),
		"Agency Host",
		"",
		"voice",
		"active",
		false,
		int64(0),
		int64(1),
		int64(8),
		int64(1),
		int64(99),
		int64(1700000000000),
		int64(1700000001000),
		int64(0),
		int64(0),
		int64(0),
		int64(0),
	)
	mock.ExpectQuery(`(?s)SELECT .*0 AS is_pinned.*0 AS pin_list_rank.*0 AS pin_weight.*0 AS pinned_until_ms.*FROM room_list_entries.*owner_user_id IN \(\?,\?\).*ORDER BY created_at_ms DESC, room_id ASC`).
		WithArgs(appcode.Default, "active", int64(6201), int64(6202), 11).
		WillReturnRows(rows)

	entries, err := repository.ListRoomListEntries(context.Background(), roomservice.RoomListQuery{
		AppCode:      appcode.Default,
		Tab:          "new",
		OwnerUserIDs: []int64{6201, 6202},
		Limit:        11,
	})
	if err != nil {
		t.Fatalf("owner list query should scan placeholder pin columns: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
	if len(entries) != 1 || entries[0].RoomID != "room-agency-owner" {
		t.Fatalf("unexpected owner list entries: %+v", entries)
	}
	if entries[0].IsPinned || entries[0].PinListRank != 0 || entries[0].PinWeight != 0 || entries[0].PinnedUntilMS != 0 {
		t.Fatalf("owner list pin placeholders must scan as zero values: %+v", entries[0])
	}
}

func TestBuildRoomListByOwnersQuerySQLKeepsHotCursorOrdering(t *testing.T) {
	sqlText, args := buildRoomListByOwnersQuerySQL(roomservice.RoomListQuery{
		AppCode:         appcode.Default,
		Tab:             "hot",
		OwnerUserIDs:    []int64{6201, 6202},
		CursorSortScore: 900,
		CursorRoomID:    "room-cursor",
		Limit:           21,
	})

	for _, fragment := range []string{
		"0 AS is_pinned",
		"0 AS pin_list_rank",
		"0 AS pin_weight",
		"0 AS pinned_until_ms",
		"owner_user_id IN (?,?)",
		"(sort_score < ? OR (sort_score = ? AND room_id > ?))",
		"ORDER BY sort_score DESC, room_id ASC",
	} {
		if !strings.Contains(sqlText, fragment) {
			t.Fatalf("owner hot SQL missing %q in:\n%s", fragment, sqlText)
		}
	}

	wantArgs := []any{appcode.Default, "active", int64(6201), int64(6202), int64(900), int64(900), "room-cursor", 21}
	if len(args) != len(wantArgs) {
		t.Fatalf("owner hot args length mismatch: got=%v want=%v", args, wantArgs)
	}
	for i := range args {
		if args[i] != wantArgs[i] {
			t.Fatalf("owner hot args[%d] mismatch: got=%v want=%v; all=%v", i, args[i], wantArgs[i], args)
		}
	}
}
