package web_test

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/stretchr/testify/require"
)

func TestUnregisteredSubjectRedirectedToRegister(t *testing.T) {
	app := newAppAs(t, "unregistered-sub")
	truncateAll(t, rawPool(t))

	rec := do(t, app, httptest.NewRequest(http.MethodGet, "/", nil))
	require.Equal(t, http.StatusFound, rec.Code)
	require.Equal(t, "/register", rec.Header().Get("Location"))
}

func TestRegisterPageReachableWhenUnregistered(t *testing.T) {
	app := newAppAs(t, "unregistered-sub")
	truncateAll(t, rawPool(t))

	rec := do(t, app, httptest.NewRequest(http.MethodGet, "/register", nil))
	require.Equal(t, http.StatusOK, rec.Code, "/register is exempt from the gate")
	require.Contains(t, body(t, rec), "Complete your registration")
}

func TestRegisteredSubjectPassesThrough(t *testing.T) {
	app := newApp(t) // stubSubject
	resetDB(t, rawPool(t))

	rec := do(t, app, httptest.NewRequest(http.MethodGet, "/", nil))
	require.Equal(t, http.StatusOK, rec.Code)
	require.Contains(t, body(t, rec), "<html", "registered subject gets the app, not a redirect")
}

func TestRegisterCreatesExactlyOneUserAndIdentity(t *testing.T) {
	ctx := context.Background()
	const sub = "brand-new-subject"
	app := newAppAs(t, sub)
	p := rawPool(t)
	truncateAll(t, p)

	req := httptest.NewRequest(http.MethodPost, "/register",
		strings.NewReader("display_name=Newbie"))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	rec := do(t, app, req)
	require.Equal(t, http.StatusSeeOther, rec.Code)
	require.Equal(t, "/", rec.Header().Get("Location"))

	// Exactly one identity row for the subject, and its user exists.
	var idents int
	var newID string
	require.NoError(t, p.QueryRow(ctx,
		`SELECT count(*), coalesce(max(user_id::text), '') FROM user_identities WHERE dex_subject = $1`,
		sub).Scan(&idents, &newID))
	require.Equal(t, 1, idents)

	var users int
	require.NoError(t, p.QueryRow(ctx, `SELECT count(*) FROM users WHERE id = $1`, newID).Scan(&users))
	require.Equal(t, 1, users)

	// Returning subject resolves straight through — no second user created.
	rec = do(t, app, httptest.NewRequest(http.MethodGet, "/", nil))
	require.Equal(t, http.StatusOK, rec.Code)

	var totalUsers, totalIdents int
	require.NoError(t, p.QueryRow(ctx, `SELECT count(*) FROM users`).Scan(&totalUsers))
	require.NoError(t, p.QueryRow(ctx, `SELECT count(*) FROM user_identities`).Scan(&totalIdents))
	require.Equal(t, 1, totalUsers, "a second request must not register again")
	require.Equal(t, 1, totalIdents)
}

// loginEventCount counts login_events for the stub user via the raw pool.
func loginEventCount(t *testing.T, p *pgxpool.Pool) int {
	t.Helper()
	var n int
	require.NoError(t, p.QueryRow(context.Background(),
		`SELECT count(*) FROM login_events WHERE user_id = $1`, userID).Scan(&n))
	return n
}

// TestGateStampsLoginEventThrottled: a gated request for a registered user stamps
// exactly one login event, and a same-day repeat is throttled to no new row — the
// read-side Stage-0 signal flowing from the registration gate.
func TestGateStampsLoginEventThrottled(t *testing.T) {
	app := newApp(t) // stubSubject → userID
	p := rawPool(t)
	resetDB(t, p)
	require.Equal(t, 0, loginEventCount(t, p))

	rec := do(t, app, httptest.NewRequest(http.MethodGet, "/", nil))
	require.Equal(t, http.StatusOK, rec.Code)
	require.Equal(t, 1, loginEventCount(t, p), "a gated request must stamp one login event")

	do(t, app, httptest.NewRequest(http.MethodGet, "/", nil))
	require.Equal(t, 1, loginEventCount(t, p), "a same-day repeat must not stamp again")
}

// TestUnregisteredSubjectIsNotStamped: a subject with no tapir user is redirected
// to /register and never reaches the stamp (no user_id to attribute it to).
func TestUnregisteredSubjectIsNotStamped(t *testing.T) {
	app := newAppAs(t, "unregistered-sub")
	p := rawPool(t)
	truncateAll(t, p)

	rec := do(t, app, httptest.NewRequest(http.MethodGet, "/", nil))
	require.Equal(t, http.StatusFound, rec.Code)

	var n int
	require.NoError(t, p.QueryRow(context.Background(),
		`SELECT count(*) FROM login_events`).Scan(&n))
	require.Equal(t, 0, n, "an unregistered subject must not stamp a login event")
}

func TestRegisterRejectsMissingFields(t *testing.T) {
	app := newAppAs(t, "incomplete-subject")
	truncateAll(t, rawPool(t))

	req := httptest.NewRequest(http.MethodPost, "/register",
		strings.NewReader("display_name=")) // missing display name
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	rec := do(t, app, req)
	require.Equal(t, http.StatusBadRequest, rec.Code)
}
