//go:build integration

package inttest

import (
	"context"
	"testing"
	"time"

	"github.com/google/uuid"

	"scrabble/backend/internal/engine"
	"scrabble/backend/internal/game"
)

// TestListForLobbyDropsLeftAIGames covers the lobby filter (game.Service.ListForLobby): a
// finished honest-AI game the player left — by resigning or by abandoning to the inactivity
// timeout — drops out of the lobby list, while it stays in ListForAccount so the admin console
// and the account-merge count keep the full set. A normally finished AI game, an active AI game
// and a left human game all stay in the lobby. The drop is a game property, not a per-resigner
// one, so it leaves every seat's lobby.
func TestListForLobbyDropsLeftAIGames(t *testing.T) {
	ctx := context.Background()
	svc := newGameService()

	createAI := func(t *testing.T) (uuid.UUID, []uuid.UUID) {
		t.Helper()
		seats := []uuid.UUID{provisionAccount(t), provisionAccount(t)}
		g, err := svc.Create(ctx, game.CreateParams{
			Variant: engine.VariantEnglish, Seats: seats, TurnTimeout: time.Hour, Seed: 1, VsAI: true,
		})
		if err != nil {
			t.Fatalf("create AI game: %v", err)
		}
		return g.ID, seats
	}

	t.Run("resign dropped from lobby, kept in account list", func(t *testing.T) {
		gid, seats := createAI(t)
		me := seats[0]
		res, err := svc.Resign(ctx, gid, me)
		if err != nil {
			t.Fatalf("resign: %v", err)
		}
		if res.Game.EndReason != "resign" {
			t.Fatalf("end reason = %q, want resign", res.Game.EndReason)
		}
		if lobbyHas(t, svc, me, gid) {
			t.Error("resigned AI game still in the lobby list")
		}
		if !containsGame(t, svc, me, gid) {
			t.Error("resigned AI game missing from the full account list (the row must stay)")
		}
		if lobbyHas(t, svc, seats[1], gid) {
			t.Error("resigned AI game still in the opponent's lobby list (drop is per-game)")
		}
	})

	t.Run("timeout dropped from lobby", func(t *testing.T) {
		gid, seats := createAI(t)
		me := seats[0] // seat 0 is to move and will be timed out
		// Disable the away window so the sweep is deterministic regardless of wall clock.
		setAway(t, me, "UTC", "00:00", "00:00")
		backdate(t, gid, time.Now().UTC().Add(-(game.AIInactivityTimeout + 2*time.Hour)))
		if n, err := svc.SweepTimeouts(ctx, time.Now().UTC()); err != nil || n < 1 {
			t.Fatalf("sweep swept %d (err %v), want >= 1", n, err)
		}
		if status, reason := gameStatus(t, svc, gid); status != game.StatusFinished || reason != "timeout" {
			t.Fatalf("game not timed out: status %q reason %q", status, reason)
		}
		if lobbyHas(t, svc, me, gid) {
			t.Error("abandoned (timed-out) AI game still in the lobby list")
		}
	})

	t.Run("active AI game stays in lobby", func(t *testing.T) {
		gid, seats := createAI(t)
		if !lobbyHas(t, svc, seats[0], gid) {
			t.Error("active AI game missing from the lobby list")
		}
	})

	t.Run("normally finished AI game stays in lobby", func(t *testing.T) {
		gid, seats := createAI(t)
		var fin game.MoveResult
		for i := 0; i < 8; i++ { // six consecutive passes end the game scoreless
			r, err := svc.Pass(ctx, gid, seats[i%2])
			if err != nil {
				t.Fatalf("pass %d: %v", i, err)
			}
			fin = r
			if r.Game.Status == game.StatusFinished {
				break
			}
		}
		if fin.Game.Status != game.StatusFinished || fin.Game.EndReason != "scoreless" {
			t.Fatalf("AI game not scoreless-finished: status %q reason %q", fin.Game.Status, fin.Game.EndReason)
		}
		if !lobbyHas(t, svc, seats[0], gid) {
			t.Error("normally finished AI game must stay in the lobby list")
		}
	})

	t.Run("left human game stays in lobby", func(t *testing.T) {
		seats := []uuid.UUID{provisionAccount(t), provisionAccount(t)}
		g, err := svc.Create(ctx, game.CreateParams{
			Variant: engine.VariantEnglish, Seats: seats, TurnTimeout: 24 * time.Hour, Seed: 1,
		})
		if err != nil {
			t.Fatalf("create human game: %v", err)
		}
		if _, err := svc.Resign(ctx, g.ID, seats[0]); err != nil {
			t.Fatalf("resign: %v", err)
		}
		if !lobbyHas(t, svc, seats[0], g.ID) {
			t.Error("resigned human game must stay in the lobby list (only AI games are dropped)")
		}
	})
}

// lobbyHas reports whether the account's own lobby list (ListForLobby) includes gameID.
func lobbyHas(t *testing.T, svc *game.Service, accountID, gameID uuid.UUID) bool {
	t.Helper()
	games, err := svc.ListForLobby(context.Background(), accountID)
	if err != nil {
		t.Fatalf("list for lobby: %v", err)
	}
	for _, g := range games {
		if g.ID == gameID {
			return true
		}
	}
	return false
}
