package main

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

	"xtablo-backend/internal/web/handlers"
)

func TestRootRedirectsToLoginWhenUnauthenticated(t *testing.T) {
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusSeeOther {
		t.Fatalf("expected status 303, got %d", rec.Code)
	}
	if location := rec.Header().Get("Location"); location != "/login" {
		t.Fatalf("expected redirect to /login, got %q", location)
	}
}

func TestUnknownRouteRedirectsToLoginWhenUnauthenticated(t *testing.T) {
	req := httptest.NewRequest(http.MethodGet, "/missing", nil)
	rec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusSeeOther {
		t.Fatalf("expected status 303, got %d", rec.Code)
	}
	if location := rec.Header().Get("Location"); location != "/login" {
		t.Fatalf("expected redirect to /login, got %q", location)
	}
}

func TestLoginPageRenders(t *testing.T) {
	req := httptest.NewRequest(http.MethodGet, "/login", nil)
	rec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}

	body := rec.Body.String()
	for _, want := range []string{
		"Se connecter à Xtablo",
		`hx-post="/login"`,
		"https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-beta2/dist/htmx.min.js",
		`href="/static/tailwind.css"`,
		`href="/pwa-icons/favicon-32x32.png"`,
		`href="/pwa-icons/favicon-16x16.png"`,
		`href="/pwa-icons/apple-touch-icon-180x180.png"`,
		`href="/manifest.webmanifest"`,
		`src="/logo_dark.png"`,
		`src="/logo_white.png"`,
		`data-testid="auth-card-shell"`,
		"Découvrez la nouvelle expérience de connexion",
		"Mot de passe oublié ?",
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected body to contain %q", want)
		}
	}
}

func TestTailwindStylesheetIsServed(t *testing.T) {
	req := httptest.NewRequest(http.MethodGet, "/static/tailwind.css", nil)
	rec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}

	body := rec.Body.String()
	for _, want := range []string{
		".text-2xl",
		".grid-cols-1",
		".whitespace-nowrap",
		".justify-end",
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected tailwind.css to contain %q", want)
		}
	}
}

func TestSignupPageRenders(t *testing.T) {
	req := httptest.NewRequest(http.MethodGet, "/signup", nil)
	rec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}

	body := rec.Body.String()
	for _, want := range []string{
		"S&#39;inscrire à Xtablo",
		`hx-post="/signup"`,
		"Vous avez déjà un compte ?",
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected body to contain %q", want)
		}
	}
}

func TestBrandingAssetsAreServed(t *testing.T) {
	testCases := []string{
		"/logo_dark.png",
		"/logo_white.png",
		"/pwa-icons/favicon-32x32.png",
		"/pwa-icons/favicon-16x16.png",
		"/pwa-icons/apple-touch-icon-180x180.png",
		"/manifest.webmanifest",
	}

	router := newTestRouter()

	for _, path := range testCases {
		req := httptest.NewRequest(http.MethodGet, path, nil)
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Fatalf("expected %s to return 200, got %d", path, rec.Code)
		}
	}
}

func TestLoginReturnsValidationError(t *testing.T) {
	form := url.Values{}
	form.Set("email", "")
	form.Set("password", "")

	req := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	rec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusUnprocessableEntity {
		t.Fatalf("expected status 422, got %d", rec.Code)
	}

	if !strings.Contains(rec.Body.String(), "Veuillez renseigner votre email et votre mot de passe") {
		t.Fatalf("expected validation error fragment, got %q", rec.Body.String())
	}
}

func TestLoginCreatesSessionAndRedirects(t *testing.T) {
	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	req := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	rec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}
	if redirect := rec.Header().Get("HX-Redirect"); redirect != "/" {
		t.Fatalf("expected HX-Redirect to /, got %q", redirect)
	}
	sessionCookie := findCookie(rec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	homeReq := httptest.NewRequest(http.MethodGet, "/", nil)
	homeReq.AddCookie(sessionCookie)
	homeRec := httptest.NewRecorder()
	router.ServeHTTP(homeRec, homeReq)

	if homeRec.Code != http.StatusOK {
		t.Fatalf("expected authenticated root status 200, got %d", homeRec.Code)
	}
	for _, want := range []string{
		"Bonjour,",
		"Aperçu",
		"https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-beta2/dist/htmx.min.js",
		`hx-get="/tasks"`,
		`hx-target="#app-main-content"`,
		`hx-swap="outerHTML"`,
		`hx-push-url="true"`,
		"Tâches",
		"Projets",
		"Planning",
		"Discussions",
		"Fichiers",
		"Feedback",
		"Arctic Matrix",
		"Créer un projet",
		"Créer une tâche",
		"Inviter l&#39;équipe",
		"Envoyer un message",
		"Mes Projets",
		"Mes Tâches",
		`action="/logout"`,
	} {
		if !strings.Contains(homeRec.Body.String(), want) {
			t.Fatalf("expected authenticated home page to contain %q, got %q", want, homeRec.Body.String())
		}
	}
}

func TestTasksPageRendersFullDashboardPage(t *testing.T) {
	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	req := httptest.NewRequest(http.MethodGet, "/tasks", nil)
	req.AddCookie(sessionCookie)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}

	body := rec.Body.String()
	for _, want := range []string{
		`class="sidebar-nav-shell"`,
		`id="app-main-content"`,
		`data-current-view="kanban"`,
		"Mes Tâches",
		"Nouvelle tâche",
		"Tableau",
		"Filtrer",
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected tasks page to contain %q", want)
		}
	}
}

func TestHomePageProjectsUseSharedTabloGridCardWithDeleteAction(t *testing.T) {
	repo := handlers.NewInMemoryAuthRepository()
	authUser, err := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
	if err != nil {
		t.Fatalf("expected demo user, got error: %v", err)
	}
	if _, err := repo.CreateTablo(context.Background(), handlers.CreateTabloInput{
		OwnerID: authUser.ID,
		Name:    "Hello",
		Status:  handlers.TabloStatusInProgress,
	}); err != nil {
		t.Fatalf("expected tablo creation to succeed, got error: %v", err)
	}

	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()

	router := newRouterWithHandler(handlers.NewAuthHandler(repo))
	router.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	req := httptest.NewRequest(http.MethodGet, "/", nil)
	req.AddCookie(sessionCookie)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}

	body := rec.Body.String()
	for _, want := range []string{
		`class="project-card"`,
		`class="project-date-row"`,
		`hx-delete="/tablos/`,
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected home page to contain %q", want)
		}
	}
}

func TestHomePageProjectsCollapseAfterSixByDefault(t *testing.T) {
	repo := handlers.NewInMemoryAuthRepository()
	authUser, err := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
	if err != nil {
		t.Fatalf("expected demo user, got error: %v", err)
	}
	for i := 0; i < 8; i++ {
		if _, err := repo.CreateTablo(context.Background(), handlers.CreateTabloInput{
			OwnerID: authUser.ID,
			Name:    "Project " + string(rune('A'+i)),
			Status:  handlers.TabloStatusTodo,
		}); err != nil {
			t.Fatalf("expected tablo creation to succeed, got error: %v", err)
		}
	}

	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()

	router := newRouterWithHandler(handlers.NewAuthHandler(repo))
	router.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	req := httptest.NewRequest(http.MethodGet, "/", nil)
	req.AddCookie(sessionCookie)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}

	body := rec.Body.String()
	if count := strings.Count(body, `class="project-card"`); count != 8 {
		t.Fatalf("expected all 8 project cards to be rendered, got %d", count)
	}
	if count := strings.Count(body, `data-overview-project-hidden="true"`); count != 2 {
		t.Fatalf("expected 2 project cards to be hidden by default, got %d", count)
	}
	for _, want := range []string{
		`id="overview-projects-section"`,
		`Voir 2 de plus`,
		`data-overview-see-more="true"`,
		`data-overview-expanded="false"`,
		`data-overview-hidden-count="2"`,
		`data-overview-hide-label="Masquer"`,
		`data-overview-chevron-down="m6 9 6 6 6-6"`,
		`data-overview-chevron-up="m6 15 6-6 6 6"`,
		`data-overview-see-more-chevron="true"`,
		`window.xtabloOverviewProjectsInitialized`,
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected home page to contain %q", want)
		}
	}
	for _, unwanted := range []string{
		`hx-get="/?show_projects=all"`,
		`hx-target="#overview-projects-section"`,
	} {
		if strings.Contains(body, unwanted) {
			t.Fatalf("expected home page to avoid HTMX see-more wiring %q", unwanted)
		}
	}
}

func TestHomePageProjectsUseVanillaJSSeeMoreToggle(t *testing.T) {
	repo := handlers.NewInMemoryAuthRepository()
	authUser, err := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
	if err != nil {
		t.Fatalf("expected demo user, got error: %v", err)
	}
	for i := 0; i < 8; i++ {
		if _, err := repo.CreateTablo(context.Background(), handlers.CreateTabloInput{
			OwnerID: authUser.ID,
			Name:    "Project " + string(rune('A'+i)),
			Status:  handlers.TabloStatusTodo,
		}); err != nil {
			t.Fatalf("expected tablo creation to succeed, got error: %v", err)
		}
	}

	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()

	router := newRouterWithHandler(handlers.NewAuthHandler(repo))
	router.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	req := httptest.NewRequest(http.MethodGet, "/?show_projects=all", nil)
	req.Header.Set("HX-Request", "true")
	req.AddCookie(sessionCookie)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}

	body := rec.Body.String()
	for _, want := range []string{
		`id="app-main-content"`,
		`id="overview-projects-section"`,
		`data-overview-see-more="true"`,
		`data-overview-expanded="false"`,
		`data-overview-project-hidden="true"`,
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected HTMX home response to contain %q", want)
		}
	}
	for _, unwanted := range []string{
		`hx-get="/?show_projects=all"`,
		`hx-target="#overview-projects-section"`,
	} {
		if strings.Contains(body, unwanted) {
			t.Fatalf("expected HTMX home response to avoid HTMX see-more wiring %q", unwanted)
		}
	}
}

func TestTasksPageReturnsHTMXMainContentSwap(t *testing.T) {
	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	req := httptest.NewRequest(http.MethodGet, "/tasks", nil)
	req.Header.Set("HX-Request", "true")
	req.AddCookie(sessionCookie)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}

	body := rec.Body.String()
	for _, want := range []string{
		`id="app-main-content"`,
		`hx-swap-oob="outerHTML"`,
		`id="sidebar-nav-tasks"`,
		`data-current-view="kanban"`,
		"Mes Tâches",
		"Nouvelle tâche",
		"Tableau",
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected HTMX tasks response to contain %q", want)
		}
	}
	if strings.Contains(body, `class="sidebar-nav-shell"`) {
		t.Fatalf("expected HTMX tasks response to avoid rerendering the full sidebar")
	}
}

func TestTasksRoutesCreatePatchAndDeleteThroughRouter(t *testing.T) {
	repo := handlers.NewInMemoryAuthRepository()
	authUser, err := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
	if err != nil {
		t.Fatalf("expected demo user, got error: %v", err)
	}

	tablo, err := repo.CreateTablo(context.Background(), handlers.CreateTabloInput{
		OwnerID: authUser.ID,
		Name:    "Router Tablo",
		Color:   "#3B82F6",
		Status:  handlers.TabloStatusTodo,
	})
	if err != nil {
		t.Fatalf("expected tablo creation to succeed, got error: %v", err)
	}

	router := newRouterWithHandler(handlers.NewAuthHandler(repo))
	sessionCookie := loginCookieForRouter(t, router)

	createForm := url.Values{}
	createForm.Set("tablo_id", tablo.ID.String())
	createForm.Set("title", "Route Task")
	createForm.Set("status", "todo")

	createReq := httptest.NewRequest(http.MethodPost, "/tasks", strings.NewReader(createForm.Encode()))
	createReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	createReq.AddCookie(sessionCookie)
	createRec := httptest.NewRecorder()
	router.ServeHTTP(createRec, createReq)

	if createRec.Code != http.StatusOK {
		t.Fatalf("expected task create status 200, got %d", createRec.Code)
	}
	if !strings.Contains(createRec.Body.String(), "Route Task") {
		t.Fatalf("expected create response to contain task title, got %q", createRec.Body.String())
	}

	tasks, err := repo.ListTasksByTablo(context.Background(), handlers.ListTasksByTabloInput{
		OwnerID: authUser.ID,
		TabloID: tablo.ID,
	})
	if err != nil || len(tasks) != 1 {
		t.Fatalf("expected one task after create, got %d / err=%v", len(tasks), err)
	}

	patchForm := url.Values{}
	patchForm.Set("title", "Updated Route Task")
	patchForm.Set("description", "Updated from router test")
	patchForm.Set("status", "done")

	patchReq := httptest.NewRequest(http.MethodPatch, "/tasks/"+tasks[0].ID.String(), strings.NewReader(patchForm.Encode()))
	patchReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	patchReq.SetPathValue("taskID", tasks[0].ID.String())
	patchReq.AddCookie(sessionCookie)
	patchRec := httptest.NewRecorder()
	router.ServeHTTP(patchRec, patchReq)

	if patchRec.Code != http.StatusOK {
		t.Fatalf("expected task patch status 200, got %d", patchRec.Code)
	}
	if !strings.Contains(patchRec.Body.String(), "Updated Route Task") {
		t.Fatalf("expected patch response to contain updated title, got %q", patchRec.Body.String())
	}

	deleteReq := httptest.NewRequest(http.MethodDelete, "/tasks/"+tasks[0].ID.String(), nil)
	deleteReq.SetPathValue("taskID", tasks[0].ID.String())
	deleteReq.AddCookie(sessionCookie)
	deleteRec := httptest.NewRecorder()
	router.ServeHTTP(deleteRec, deleteReq)

	if deleteRec.Code != http.StatusOK {
		t.Fatalf("expected task delete status 200, got %d", deleteRec.Code)
	}
	if strings.Contains(deleteRec.Body.String(), "Updated Route Task") {
		t.Fatalf("expected delete response to remove updated task, got %q", deleteRec.Body.String())
	}
}

func TestTablosPageRendersFullDashboardPage(t *testing.T) {
	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	req := httptest.NewRequest(http.MethodGet, "/tablos", nil)
	req.AddCookie(sessionCookie)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}

	body := rec.Body.String()
	for _, want := range []string{
		`class="sidebar-nav-shell"`,
		`id="app-main-content" class="flex-1 overflow-auto"`,
		"Mes Projets",
		"Nouveau projet",
		"Vue en grille",
		"Rechercher...",
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected tablos page to contain %q", want)
		}
	}
}

func TestTablosPageReturnsHTMXMainContentSwap(t *testing.T) {
	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	req := httptest.NewRequest(http.MethodGet, "/tablos?view=list&status=all", nil)
	req.Header.Set("HX-Request", "true")
	req.AddCookie(sessionCookie)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}

	body := rec.Body.String()
	for _, want := range []string{
		`id="app-main-content"`,
		`hx-swap-oob="outerHTML"`,
		`id="sidebar-nav-tablos"`,
		"Mes Projets",
		"Vue en liste",
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected HTMX tablos response to contain %q", want)
		}
	}
	if strings.Contains(body, `class="sidebar-nav-shell"`) {
		t.Fatalf("expected HTMX tablos response to avoid rerendering the full sidebar")
	}
}

func TestTablosPageUtilityStylesExist(t *testing.T) {
	content, err := os.ReadFile("static/tailwind.css")
	if err != nil {
		t.Fatalf("read tailwind.css: %v", err)
	}

	css := string(content)
	for _, want := range []string{
		".flex-1",
		".overflow-auto",
		".text-2xl",
		".bg-purple-600",
		".grid-cols-1",
		".rounded-xl",
		".md\\:flex-row",
		".sm\\:grid-cols-2",
		".lg\\:grid-cols-3",
		".xl\\:grid-cols-4",
	} {
		if !strings.Contains(css, want) {
			t.Fatalf("expected tailwind.css to contain utility %q", want)
		}
	}
}

func TestSignupCreatesUserSessionAndRedirects(t *testing.T) {
	form := url.Values{}
	form.Set("email", "new@xtablo.com")
	form.Set("password", "xtablo-secret")

	req := httptest.NewRequest(http.MethodPost, "/signup", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	rec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("expected status 200, got %d", rec.Code)
	}
	if redirect := rec.Header().Get("HX-Redirect"); redirect != "/" {
		t.Fatalf("expected HX-Redirect to /, got %q", redirect)
	}
	sessionCookie := findCookie(rec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()
	router.ServeHTTP(loginRec, loginReq)
	if loginRec.Header().Get("HX-Redirect") != "/" {
		t.Fatalf("expected signed up user to be able to log in")
	}
}

func TestLogoutClearsSessionAndRedirectsToLogin(t *testing.T) {
	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	logoutReq := httptest.NewRequest(http.MethodPost, "/logout", nil)
	logoutReq.AddCookie(sessionCookie)
	logoutRec := httptest.NewRecorder()
	router.ServeHTTP(logoutRec, logoutReq)

	if logoutRec.Code != http.StatusSeeOther {
		t.Fatalf("expected logout status 303, got %d", logoutRec.Code)
	}
	if location := logoutRec.Header().Get("Location"); location != "/login" {
		t.Fatalf("expected logout redirect to /login, got %q", location)
	}

	clearedCookie := findCookie(logoutRec.Result().Cookies(), "xtablo_session")
	if clearedCookie == nil {
		t.Fatalf("expected cleared session cookie")
	}
	if clearedCookie.MaxAge >= 0 && clearedCookie.Value != "" {
		t.Fatalf("expected cleared session cookie to be expired, got %+v", clearedCookie)
	}

	homeReq := httptest.NewRequest(http.MethodGet, "/", nil)
	homeReq.AddCookie(sessionCookie)
	homeRec := httptest.NewRecorder()
	router.ServeHTTP(homeRec, homeReq)

	if homeRec.Code != http.StatusSeeOther {
		t.Fatalf("expected logged-out root access to redirect, got %d", homeRec.Code)
	}
	if location := homeRec.Header().Get("Location"); location != "/login" {
		t.Fatalf("expected logged-out root redirect to /login, got %q", location)
	}
}

func TestUnknownRouteShowsDashboard404WhenAuthenticated(t *testing.T) {
	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()

	router := newTestRouter()
	router.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	req := httptest.NewRequest(http.MethodGet, "/missing", nil)
	req.AddCookie(sessionCookie)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusNotFound {
		t.Fatalf("expected status 404, got %d", rec.Code)
	}

	body := rec.Body.String()
	for _, want := range []string{
		"Aperçu",
		"Page introuvable",
		"Cette page n'existe pas",
		`href="/"`,
		`action="/logout"`,
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("expected authenticated 404 page to contain %q", want)
		}
	}
}

func TestSessionSurvivesHandlerRecreation(t *testing.T) {
	repo := handlers.NewInMemoryAuthRepository()

	loginRouter := newRouterWithHandler(handlers.NewAuthHandler(repo))

	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()
	loginRouter.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatalf("expected session cookie to be set")
	}

	reloadedRouter := newRouterWithHandler(handlers.NewAuthHandler(repo))

	homeReq := httptest.NewRequest(http.MethodGet, "/", nil)
	homeReq.AddCookie(sessionCookie)
	homeRec := httptest.NewRecorder()
	reloadedRouter.ServeHTTP(homeRec, homeReq)

	if homeRec.Code != http.StatusOK {
		t.Fatalf("expected session to survive handler recreation, got status %d", homeRec.Code)
	}
}

func findCookie(cookies []*http.Cookie, name string) *http.Cookie {
	for _, cookie := range cookies {
		if cookie.Name == name {
			return cookie
		}
	}
	return nil
}

func loginCookieForRouter(t *testing.T, router http.Handler) *http.Cookie {
	t.Helper()

	form := url.Values{}
	form.Set("email", "demo@xtablo.com")
	form.Set("password", "xtablo-demo")

	loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
	loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	loginRec := httptest.NewRecorder()
	router.ServeHTTP(loginRec, loginReq)

	sessionCookie := findCookie(loginRec.Result().Cookies(), "xtablo_session")
	if sessionCookie == nil {
		t.Fatal("expected session cookie to be set")
	}
	return sessionCookie
}
