package user

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
	"github.com/stretchr/testify/require"
)

// mountHandler builds a chi router with the user Handler attached. Helper
// is shared by every handler test so the route prefix /api/v1/auth lives in
// exactly one place; if the URL contract ever shifts the change is local.
func mountHandler(svc Service) http.Handler {
	r := chi.NewRouter()
	h := NewHandler(svc)
	h.Mount(r)
	return r
}

// TestHandler_LoginThenMeThenLogout walks the full happy path of the auth
// surface: log in, hit /me with the issued token, log out, and confirm the
// token is no longer accepted. It exercises the integration between Handler,
// Service, the in-memory fakeRepo and the Auth middleware.
func TestHandler_LoginThenMeThenLogout(t *testing.T) {
	repo := newFakeRepo()
	hash, err := HashPassword("password123")
	require.NoError(t, err)
	repo.users["a@b.c"] = &User{
		ID:           uuid.New(),
		Email:        "a@b.c",
		DisplayName:  "T",
		PasswordHash: hash,
	}
	svc := NewService(repo, nil)
	srv := httptest.NewServer(mountHandler(svc))
	defer srv.Close()

	// 1. login
	body, err := json.Marshal(map[string]string{"email": "a@b.c", "password": "password123"})
	require.NoError(t, err)
	resp, err := http.Post(srv.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(body))
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, resp.StatusCode)
	var loginOut struct {
		Token string `json:"token"`
		User  struct {
			ID    string `json:"id"`
			Email string `json:"email"`
		} `json:"user"`
	}
	require.NoError(t, json.NewDecoder(resp.Body).Decode(&loginOut))
	require.NoError(t, resp.Body.Close())
	require.NotEmpty(t, loginOut.Token)
	require.Equal(t, "a@b.c", loginOut.User.Email)

	// 2. me
	req, err := http.NewRequest(http.MethodGet, srv.URL+"/api/v1/auth/me", nil)
	require.NoError(t, err)
	req.Header.Set("Authorization", "Bearer "+loginOut.Token)
	resp, err = http.DefaultClient.Do(req)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, resp.StatusCode)
	require.NoError(t, resp.Body.Close())

	// 3. logout
	req, err = http.NewRequest(http.MethodPost, srv.URL+"/api/v1/auth/logout", nil)
	require.NoError(t, err)
	req.Header.Set("Authorization", "Bearer "+loginOut.Token)
	resp, err = http.DefaultClient.Do(req)
	require.NoError(t, err)
	require.Equal(t, http.StatusNoContent, resp.StatusCode)
	require.NoError(t, resp.Body.Close())

	// 4. me again -> 401: the same bearer must be rejected after logout.
	req, err = http.NewRequest(http.MethodGet, srv.URL+"/api/v1/auth/me", nil)
	require.NoError(t, err)
	req.Header.Set("Authorization", "Bearer "+loginOut.Token)
	resp, err = http.DefaultClient.Do(req)
	require.NoError(t, err)
	require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
	require.NoError(t, resp.Body.Close())
}

// TestHandler_LoginInvalidPayload covers the validation branch: an empty
// JSON object lacks both email and password and must be rejected with 400
// before the service layer is consulted.
func TestHandler_LoginInvalidPayload(t *testing.T) {
	svc := NewService(newFakeRepo(), nil)
	srv := httptest.NewServer(mountHandler(svc))
	defer srv.Close()

	resp, err := http.Post(srv.URL+"/api/v1/auth/login", "application/json", bytes.NewReader([]byte("{}")))
	require.NoError(t, err)
	require.Equal(t, http.StatusBadRequest, resp.StatusCode)
	require.NoError(t, resp.Body.Close())
}
