package web

import (
	"bytes"
	"image/png"
	"net/http"
	"net/http/httptest"
	"net/url"
	"regexp"
	"strings"
	"testing"
)

// The casino had no share card for as long as it existed, and the reason is worth
// keeping in a test rather than in a memory: every route was behind sign-in, so a
// link pasted into a chat window unfurled as whatever the *auth screen* said. Meta
// tags on a page nobody unauthenticated can fetch are meta tags nobody reads.
//
// So the two things these tests hold down are: a stranger gets a real page at the
// front door, and a stranger gets the picture. Everything else stays shut.

func TestTheDoorIsOpenToStrangers(t *testing.T) {
	s := newCasino(t)
	mux := http.NewServeMux()
	s.casinoRoutes(mux)

	w := httptest.NewRecorder()
	mux.ServeHTTP(w, httptest.NewRequest("GET", "/games", nil))

	if w.Code != http.StatusOK {
		t.Fatalf("GET /games as a stranger = %d, want 200: the front door has to be a page an unfurl bot can read", w.Code)
	}
	body := w.Body.String()
	for _, want := range []string{
		`property="og:image"`,
		`property="og:title"`,
		`property="og:description"`,
		`name="twitter:card"`,
		"og.png",
		"Sign in",
	} {
		if !strings.Contains(body, want) {
			t.Errorf("the door is missing %q", want)
		}
	}
	if strings.Contains(body, "unknown page") {
		t.Fatal("games_door is not in the games template set in server.go")
	}
	// A door you can play from is not a door.
	if strings.Contains(body, `data-move=`) {
		t.Error("the door is serving a table to somebody who has not signed in")
	}
}

func TestTheTablesStayShutToStrangers(t *testing.T) {
	s := newCasino(t)
	mux := http.NewServeMux()
	s.casinoRoutes(mux)

	for _, path := range []string{"/games/blackjack", "/games/holdem", "/games/uno", "/games/trivia", "/games/hangman", "/games/solitaire"} {
		w := httptest.NewRecorder()
		mux.ServeHTTP(w, httptest.NewRequest("GET", path, nil))
		if w.Code != http.StatusFound {
			t.Errorf("GET %s as a stranger = %d, want a 302 to sign-in", path, w.Code)
		}
	}
}

func TestTheShareCardNeedsNoSignIn(t *testing.T) {
	s := newCasino(t)
	mux := http.NewServeMux()
	s.casinoRoutes(mux)

	w := httptest.NewRecorder()
	mux.ServeHTTP(w, httptest.NewRequest("GET", "/games/og.png", nil))

	if w.Code != http.StatusOK {
		t.Fatalf("GET /games/og.png as a stranger = %d, want 200", w.Code)
	}
	if ct := w.Header().Get("Content-Type"); ct != "image/png" {
		t.Errorf("Content-Type = %q, want image/png", ct)
	}
	img, err := png.Decode(bytes.NewReader(w.Body.Bytes()))
	if err != nil {
		t.Fatalf("the share card is not a PNG: %v", err)
	}
	if b := img.Bounds(); b.Dx() != ogWidth || b.Dy() != ogHeight {
		t.Errorf("share card is %dx%d, want %dx%d — every unfurler crops to that", b.Dx(), b.Dy(), ogWidth, ogHeight)
	}
}

// An og:image that 404s is worth exactly as much as no og:image, and the two ways
// the casino is reachable disagree about where the picture lives: on the games host
// it's /og.png (hostRouter puts the /games back on), and anywhere else it's
// /games/og.png. So take the URL the page actually advertises and go and get it.
func TestTheAdvertisedShareCardIsReallyThere(t *testing.T) {
	for _, host := range []string{"", "games.parodia.dev"} {
		name := "no games host"
		if host != "" {
			name = host
		}
		t.Run(name, func(t *testing.T) {
			s := newCasino(t)
			s.cfg.Games.Host = host

			mux := http.NewServeMux()
			s.casinoRoutes(mux)
			srv := s.hostRouter(mux) // what production actually serves

			// Ask the door where its picture is.
			w := httptest.NewRecorder()
			door := httptest.NewRequest("GET", "/games", nil)
			if host != "" {
				door.Host = host
			}
			srv.ServeHTTP(w, door)
			img := ogImageURL(t, w.Body.String())

			// Then go and fetch it, the way a chat server would.
			u, err := url.Parse(img)
			if err != nil {
				t.Fatalf("og:image %q is not a URL: %v", img, err)
			}
			if u.Host == "" {
				t.Fatalf("og:image %q is relative — no unfurler will resolve it", img)
			}
			w = httptest.NewRecorder()
			get := httptest.NewRequest("GET", u.Path, nil)
			get.Host = u.Host
			srv.ServeHTTP(w, get)

			if w.Code != http.StatusOK {
				t.Fatalf("the door advertises og:image %s, and fetching it gives %d", img, w.Code)
			}
			if ct := w.Header().Get("Content-Type"); ct != "image/png" {
				t.Errorf("og:image %s served %q, want image/png", img, ct)
			}
		})
	}
}

func ogImageURL(t *testing.T, body string) string {
	t.Helper()
	m := regexp.MustCompile(`property="og:image" content="([^"]+)"`).FindStringSubmatch(body)
	if m == nil {
		t.Fatal("the door serves no og:image at all")
	}
	return m[1]
}

// Both rooms have to have a card, because the room is picked at request time and a
// missing one serves zero bytes rather than an error.
func TestTheShareCardKnowsBothRooms(t *testing.T) {
	cards, err := drawShareCards()
	if err != nil {
		t.Fatal(err)
	}
	for _, rm := range []room{roomDay, roomNight} {
		if len(cards[rm.Slug]) == 0 {
			t.Errorf("no share card for %s", rm.Slug)
		}
	}
	if len(cards) != len(ogPalettes) {
		t.Errorf("drew %d cards for %d palettes", len(cards), len(ogPalettes))
	}
}
