package service

import (
	"context"
	"testing"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/services/room-service/internal/room/cell"
	"hyapp/services/room-service/internal/room/rank"
	"hyapp/services/room-service/internal/room/state"
	"hyapp/services/room-service/internal/router"
)

func TestLoadedRoomLeaseDropsCellWhenLeaseTokenChanges(t *testing.T) {
	ctx := appcode.WithContext(context.Background(), "lalu")
	roomID := "room-token-gap"
	now := time.Date(2026, 7, 7, 9, 0, 0, 0, time.UTC)
	directory := router.NewMemoryDirectory()
	svc := &Service{
		nodeID:    "room-node-a",
		leaseTTL:  time.Second,
		directory: directory,
		cells:     make(map[string]*loadedCell),
	}

	lease, err := directory.EnsureOwner(ctx, runtimeRoomKey("lalu", roomID), svc.nodeID, now, svc.leaseTTL)
	if err != nil {
		t.Fatalf("ensure first lease: %v", err)
	}
	roomState := state.NewRoomState(roomID, 1001, 8, "voice")
	roomState.Version = 7
	svc.installCell(ctx, roomID, cell.New(roomState, rank.FromState(roomState.GiftRank, 20)), lease.LeaseToken)
	if got := svc.loadCellForLease(ctx, roomID, lease); got == nil {
		t.Fatal("freshly installed cell should be reusable with the original lease token")
	}

	if _, owned, err := svc.loadedRoomLease(ctx, loadedRoomRef{AppCode: "lalu", RoomID: roomID}, now.Add(2*time.Second)); err != nil {
		t.Fatalf("load expired lease: %v", err)
	} else if owned {
		t.Fatal("expired lease re-acquisition must not keep using the old in-memory cell")
	}
	if got := svc.loadCell(ctx, roomID); got != nil {
		t.Fatal("old in-memory cell must be forgotten after a lease-token discontinuity")
	}

	currentLease, exists, err := directory.Lookup(ctx, runtimeRoomKey("lalu", roomID))
	if err != nil {
		t.Fatalf("lookup current lease: %v", err)
	}
	if !exists || currentLease.LeaseToken == lease.LeaseToken {
		t.Fatal("test setup expected the directory to issue a new lease token after expiry")
	}
}

func TestLoadCellForCurrentOwnerRejectsReadOnlyAndExpiredCells(t *testing.T) {
	ctx := appcode.WithContext(context.Background(), "lalu")
	roomID := "room-read-current-owner"
	now := time.Date(2026, 7, 7, 10, 0, 0, 0, time.UTC)
	directory := router.NewMemoryDirectory()
	svc := &Service{
		nodeID:    "room-node-a",
		leaseTTL:  time.Second,
		clock:     fixedRoomTestClock{now: now},
		directory: directory,
		cells:     make(map[string]*loadedCell),
	}
	roomState := state.NewRoomState(roomID, 1001, 8, "voice")

	lease, err := directory.EnsureOwner(ctx, runtimeRoomKey("lalu", roomID), svc.nodeID, now, svc.leaseTTL)
	if err != nil {
		t.Fatalf("ensure lease: %v", err)
	}
	svc.installCell(ctx, roomID, cell.New(roomState.Clone(), rank.FromState(roomState.GiftRank, 20)), "")
	if got, err := svc.loadCellForCurrentOwner(ctx, roomID); err != nil {
		t.Fatalf("load read-only token cell: %v", err)
	} else if got != nil {
		t.Fatal("read-only recovered cell must not be reused as current-owner state")
	}
	if got := svc.loadCell(ctx, roomID); got != nil {
		t.Fatal("read-only cell should be forgotten after current-owner token check")
	}

	svc.installCell(ctx, roomID, cell.New(roomState.Clone(), rank.FromState(roomState.GiftRank, 20)), lease.LeaseToken)
	svc.clock = fixedRoomTestClock{now: now.Add(2 * time.Second)}
	if got, err := svc.loadCellForCurrentOwner(ctx, roomID); err != nil {
		t.Fatalf("load expired token cell: %v", err)
	} else if got != nil {
		t.Fatal("expired owner lease must not authorize reading the old in-memory cell")
	}
}

type fixedRoomTestClock struct {
	now time.Time
}

func (c fixedRoomTestClock) Now() time.Time {
	return c.now
}
