package web

import (
	"context"
	"encoding/json"
	"fmt"
	"io/fs"
	"net"
	"net/http"
	"os"
	"testing"
	"time"

	"pete/internal/games/trivia"
	"pete/internal/opentdb"
	"pete/internal/storage"
)

// TestDevCasino is not a test. It is the casino, running, on a port, with one
// signed-in player who has chips — so the table can be driven in a real browser,
// which is the only honest way to review an animation.
//
// Skipped unless you ask for it:
//
//	PETE_DEV_CASINO=:7788 go test ./internal/web -run TestDevCasino -timeout 0
//
// It prints the session cookie to plant. The routes are wired here rather than
// taken from New(), because New() decides whether the casino exists at the
// moment it builds the mux, and the test rig only signs the player in afterwards.
func TestDevCasino(t *testing.T) {
	addr := os.Getenv("PETE_DEV_CASINO")
	if addr == "" {
		t.Skip("set PETE_DEV_CASINO=:port to run the casino for a browser")
	}

	s := newCasino(t)
	fund(t, 5000)
	fundUser(t, bobPlayer, 5000)
	seedTriviaBank(t)

	// The full table runtime, so the turn clock and the reaper are live under the
	// browser exactly as in production.
	s.StartTableClock(context.Background())

	cookie := devCookie(s, "reala", "Reala")
	// A second player, so a shared table can be reviewed — hold'em is multiplayer
	// now, and one browser cannot see two people at the felt. Plant this cookie in
	// a second browser profile (or a private window) to sit down as Bob.
	bobCookie := devCookie(s, "bob", "Bob")

	staticSub, err := fs.Sub(staticFS, "static")
	if err != nil {
		t.Fatal(err)
	}

	mux := http.NewServeMux()
	mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticSub))))
	s.casinoRoutes(mux)

	ln, err := net.Listen("tcp", addr)
	if err != nil {
		t.Fatal(err)
	}
	// Written to a file, not printed: `go test` buffers stdout, and the browser
	// driver needs the cookie while the server is still running. The second cookie
	// rides alongside it, newline-separated, for the second browser.
	if out := os.Getenv("PETE_DEV_COOKIE_FILE"); out != "" {
		if err := os.WriteFile(out, []byte(cookie+"\n"+bobCookie), 0o600); err != nil {
			t.Fatal(err)
		}
	}
	fmt.Printf("\nCASINO  http://localhost%s/games\nCOOKIE  %s=%s\nBOB     %s=%s\n\n",
		addr, sessionCookie, cookie, sessionCookie, bobCookie)

	srv := &http.Server{Handler: mux, ReadHeaderTimeout: 5 * time.Second}
	t.Cleanup(func() { _ = srv.Close() })
	_ = srv.Serve(ln)
}

// devCookie mints a signed session for a player the rig has funded, so the felt
// can be driven as them.
func devCookie(s *Server, username, name string) string {
	payload, _ := json.Marshal(SessionUser{
		Sub: "sub-" + username, Username: username, Name: name,
		Exp: time.Now().Add(24 * time.Hour).Unix(),
	})
	return s.auth.sign(payload)
}

// seedTriviaBank puts enough questions in the bank to deal a ladder of each
// difficulty.
//
// The rig does not run StartTriviaBank — a dev casino that spends its first two
// minutes dripping four hundred questions per difficulty out of a free API is a
// dev casino you cannot use. But a fresh database has an empty bank, and an
// empty bank means every start 503s, so the rig would be unable to show you the
// one game it exists to show you.
//
// One real batch per difficulty, through the real client: fifty questions is
// four ladders' worth, and it means what the browser renders came out of OpenTDB
// and through the same decode-and-store path production uses, entities and all.
func seedTriviaBank(t *testing.T) {
	t.Helper()
	ctx := context.Background()
	client := opentdb.New()

	for i, tier := range trivia.Tiers {
		have, err := storage.CountTrivia(tier.Difficulty)
		if err != nil {
			t.Fatal(err)
		}
		if have >= trivia.Rungs {
			continue
		}
		if i > 0 {
			time.Sleep(opentdb.Politeness) // the API asks; asking faster earns nothing
		}
		qs, err := client.Fetch(ctx, tier.Difficulty, opentdb.Batch)
		if err != nil {
			t.Fatalf("seeding the trivia bank (%s): %v", tier.Difficulty, err)
		}
		added, err := storage.AddTriviaQuestions(tier.Difficulty, qs)
		if err != nil {
			t.Fatal(err)
		}
		fmt.Printf("BANK    %-6s %d questions\n", tier.Difficulty, added)
	}
}
