package middleware

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

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/treytartt/honeydue-api/internal/config"
	"github.com/treytartt/honeydue-api/internal/models"
)

func TestGetAuthUser_NilContext_ReturnsNil(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	// No user set in context
	user := GetAuthUser(c)
	assert.Nil(t, user)
}

func TestGetAuthUser_WrongType_ReturnsNil(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	// Set wrong type in context — should NOT panic
	c.Set(AuthUserKey, "not-a-user")
	user := GetAuthUser(c)
	assert.Nil(t, user)
}

func TestGetAuthUser_ValidUser_ReturnsUser(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	expected := &models.User{Username: "testuser"}
	c.Set(AuthUserKey, expected)

	user := GetAuthUser(c)
	require.NotNil(t, user)
	assert.Equal(t, "testuser", user.Username)
}

func TestMustGetAuthUser_Nil_Returns401(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	user, err := MustGetAuthUser(c)
	assert.Nil(t, user)
	assert.Error(t, err)
}

func TestMustGetAuthUser_WrongType_Returns401(t *testing.T) {
	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	c.Set(AuthUserKey, 12345)
	user, err := MustGetAuthUser(c)
	assert.Nil(t, user)
	assert.Error(t, err)
}

func TestTokenTruncation_ShortToken_NoPanic(t *testing.T) {
	// Ensure truncateToken does not panic on short tokens
	assert.NotPanics(t, func() {
		result := truncateToken("ab")
		assert.Equal(t, "ab...", result)
	})
}

func TestTokenTruncation_EmptyToken_NoPanic(t *testing.T) {
	assert.NotPanics(t, func() {
		result := truncateToken("")
		assert.Equal(t, "...", result)
	})
}

func TestTokenTruncation_LongToken_Truncated(t *testing.T) {
	result := truncateToken("abcdefghijklmnop")
	assert.Equal(t, "abcdefgh...", result)
}

func TestAdminAuth_QueryParamToken_Rejected(t *testing.T) {
	// SEC-20: Admin JWT via query parameter must be rejected.
	// Tokens in URLs leak into server logs and browser history.
	cfg := &config.Config{
		Security: config.SecurityConfig{SecretKey: "test-secret"},
	}

	mw := AdminAuthMiddleware(cfg, nil)
	handler := mw(func(c echo.Context) error {
		return c.String(http.StatusOK, "should not reach here")
	})

	e := echo.New()

	// Request with token only in query param, no Authorization header
	req := httptest.NewRequest(http.MethodGet, "/admin/test?token=some-jwt-token", nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	err := handler(c)
	assert.NoError(t, err) // handler writes JSON directly, no Echo error
	assert.Equal(t, http.StatusUnauthorized, rec.Code, "query param token must be rejected")
	assert.Contains(t, rec.Body.String(), "Authorization required")
}
