//go:build test

package mw

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

	"crussell/testutils/jwt"
)

func testHandler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		userID, _ := GetUserID(r.Context())
		role, _ := GetUserRole(r.Context())
		jti, _ := GetJTI(r.Context())
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(userID + "|" + role + "|" + jti))
	})
}

// =============================================================================
// RequireAuth
// =============================================================================

func TestRequireAuth_NoHeader(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	w := httptest.NewRecorder()
	RequireAuth(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("expected 401, got %d", w.Code)
	}
}

func TestRequireAuth_MalformedHeader(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "NotBearer token123")
	w := httptest.NewRecorder()
	RequireAuth(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("expected 401, got %d", w.Code)
	}
}

func TestRequireAuth_EmptyToken(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer ")
	w := httptest.NewRecorder()
	RequireAuth(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("expected 401, got %d", w.Code)
	}
}

func TestRequireAuth_InvalidToken(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer this.is.not.a.valid.jwt")
	w := httptest.NewRecorder()
	RequireAuth(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("expected 401, got %d", w.Code)
	}
}

func TestRequireAuth_ValidToken(t *testing.T) {
	token := jwt.GenerateTestToken("testuser123", "verified_email")

	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer "+token)
	w := httptest.NewRecorder()
	RequireAuth(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	body := w.Body.String()
	if body == "" || body == "|" {
		t.Errorf("expected user info in response, got %q", body)
	}
}

func TestRequireAuth_AdminToken(t *testing.T) {
	token := jwt.GenerateAdminToken()

	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer "+token)
	w := httptest.NewRecorder()
	RequireAuth(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
	}
}

func TestRequireAuth_SetsAllContextFields(t *testing.T) {
	token := jwt.GenerateTestToken("testuser123", "admin")

	var capturedID, capturedRole, capturedJTI string
	collector := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		capturedID, _ = GetUserID(r.Context())
		capturedRole, _ = GetUserRole(r.Context())
		capturedJTI, _ = GetJTI(r.Context())
		w.WriteHeader(http.StatusOK)
	})

	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer "+token)
	w := httptest.NewRecorder()
	RequireAuth(collector).ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}
	if capturedID != "testuser123" {
		t.Errorf("expected userID 'testuser123', got %q", capturedID)
	}
	if capturedRole != "admin" {
		t.Errorf("expected role 'admin', got %q", capturedRole)
	}
	if capturedJTI == "" {
		t.Error("expected non-empty JTI")
	}
}

// =============================================================================
// OptionalAuth
// =============================================================================

func TestOptionalAuth_NoHeader(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	w := httptest.NewRecorder()
	OptionalAuth(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200 (pass-through), got %d", w.Code)
	}

	body := w.Body.String()
	if body != "||" {
		t.Errorf("expected empty context (||), got %q", body)
	}
}

func TestOptionalAuth_ValidToken(t *testing.T) {
	token := jwt.GenerateTestToken("testuser123", "verified_email")

	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer "+token)
	w := httptest.NewRecorder()
	OptionalAuth(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", w.Code)
	}

	body := w.Body.String()
	if body == "||" {
		t.Error("expected context to be set with valid token")
	}
}

func TestOptionalAuth_InvalidToken(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer obviously.invalid.token")
	w := httptest.NewRecorder()
	OptionalAuth(testHandler()).ServeHTTP(w, req)

	// Should pass through without setting context
	if w.Code != http.StatusOK {
		t.Errorf("expected 200 (pass-through on invalid token), got %d", w.Code)
	}

	body := w.Body.String()
	if body != "||" {
		t.Errorf("expected empty context on invalid token, got %q", body)
	}
}

func TestOptionalAuth_NoBearer(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Basic somecreds")
	w := httptest.NewRecorder()
	OptionalAuth(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200 (pass-through without Bearer prefix), got %d", w.Code)
	}
}

// =============================================================================
// RequireRole
// =============================================================================

func TestRequireRole_Allowed(t *testing.T) {
	handler := RequireRole("admin")(testHandler())

	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "admin")
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200 for admin role, got %d", w.Code)
	}
}

func TestRequireRole_MultipleRoles(t *testing.T) {
	handler := RequireRole("admin", "verified_email")(testHandler())

	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "verified_email")
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200 for verified_email in allowed list, got %d", w.Code)
	}
}

func TestRequireRole_Forbidden(t *testing.T) {
	handler := RequireRole("admin")(testHandler())

	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "verified_email")
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)

	if w.Code != http.StatusForbidden {
		t.Errorf("expected 403 for non-admin role, got %d", w.Code)
	}
}

func TestRequireRole_NoRoleInContext(t *testing.T) {
	handler := RequireRole("admin")(testHandler())

	req := httptest.NewRequest("GET", "/", nil)
	// No UserRoleKey in context
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("expected 401 when no role in context, got %d", w.Code)
	}
}

func TestRequireRole_EmptyAllowedList(t *testing.T) {
	handler := RequireRole()(testHandler())

	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "admin")
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)

	if w.Code != http.StatusForbidden {
		t.Errorf("expected 403 with empty allowed list, got %d", w.Code)
	}
}

// =============================================================================
// RequireVerified (wraps RequireRole("verified_email", "admin"))
// =============================================================================

func TestRequireVerified_VerifiedEmail(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "verified_email")
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	RequireVerified(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200 for verified_email, got %d", w.Code)
	}
}

func TestRequireVerified_Admin(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "admin")
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	RequireVerified(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200 for admin, got %d", w.Code)
	}
}

func TestRequireVerified_Unverified(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "unverified_email")
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	RequireVerified(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusForbidden {
		t.Errorf("expected 403 for unverified, got %d", w.Code)
	}
}

// =============================================================================
// RequireAdmin (RequireRole("admin") + UserIDKey guard)
// =============================================================================

func TestRequireAdmin_AdminRole(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "admin")
	ctx = context.WithValue(ctx, UserIDKey, "adminuser001")
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	RequireAdmin(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200 for admin, got %d", w.Code)
	}
}

func TestRequireAdmin_NonAdmin(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "verified_email")
	ctx = context.WithValue(ctx, UserIDKey, "someuser001")
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	RequireAdmin(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusForbidden {
		t.Errorf("expected 403 for non-admin, got %d", w.Code)
	}
}

func TestRequireAdmin_NoUserIDInContext(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "admin")
	// No UserIDKey set
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	RequireAdmin(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("expected 401 when UserID missing from context, got %d", w.Code)
	}
}

func TestRequireAdmin_EmptyUserID(t *testing.T) {
	req := httptest.NewRequest("GET", "/", nil)
	ctx := context.WithValue(req.Context(), UserRoleKey, "admin")
	ctx = context.WithValue(ctx, UserIDKey, "")
	req = req.WithContext(ctx)
	w := httptest.NewRecorder()
	RequireAdmin(testHandler()).ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("expected 401 for empty UserID, got %d", w.Code)
	}
}

// =============================================================================
// Context helpers — GetUserID, GetUserRole, GetJTI
// =============================================================================

func TestGetUserID_Present(t *testing.T) {
	ctx := context.WithValue(context.Background(), UserIDKey, "abc123")
	id, ok := GetUserID(ctx)
	if !ok {
		t.Error("expected ok=true")
	}
	if id != "abc123" {
		t.Errorf("expected 'abc123', got %q", id)
	}
}

func TestGetUserID_Missing(t *testing.T) {
	_, ok := GetUserID(context.Background())
	if ok {
		t.Error("expected ok=false")
	}
}

func TestGetUserRole_Present(t *testing.T) {
	ctx := context.WithValue(context.Background(), UserRoleKey, "admin")
	role, ok := GetUserRole(ctx)
	if !ok {
		t.Error("expected ok=true")
	}
	if role != "admin" {
		t.Errorf("expected 'admin', got %q", role)
	}
}

func TestGetUserRole_Missing(t *testing.T) {
	_, ok := GetUserRole(context.Background())
	if ok {
		t.Error("expected ok=false")
	}
}

func TestGetJTI_Present(t *testing.T) {
	ctx := context.WithValue(context.Background(), JTIKey, "jti_test_123")
	jti, ok := GetJTI(ctx)
	if !ok {
		t.Error("expected ok=true")
	}
	if jti != "jti_test_123" {
		t.Errorf("expected 'jti_test_123', got %q", jti)
	}
}

func TestGetJTI_Missing(t *testing.T) {
	_, ok := GetJTI(context.Background())
	if ok {
		t.Error("expected ok=false")
	}
}

// =============================================================================
// Full middleware chain — RequireAuth → RequireAdmin
// =============================================================================

func TestAuthChain_AdminTokenFullFlow(t *testing.T) {
	token := jwt.GenerateAdminToken()

	handler := RequireAuth(RequireAdmin(testHandler()))

	req := httptest.NewRequest("GET", "/admin/dashboard", nil)
	req.Header.Set("Authorization", "Bearer "+token)
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200 for admin token through full chain, got %d: %s", w.Code, w.Body.String())
	}
}

func TestAuthChain_VerifiedTokenBlockedByRequireAdmin(t *testing.T) {
	token := jwt.GenerateTestToken("user123abc", "verified_email")

	handler := RequireAuth(RequireAdmin(testHandler()))

	req := httptest.NewRequest("GET", "/admin/dashboard", nil)
	req.Header.Set("Authorization", "Bearer "+token)
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)

	if w.Code != http.StatusForbidden {
		t.Errorf("expected 403 for verified_email through RequireAdmin, got %d", w.Code)
	}
}

func TestAuthChain_NoTokenBlockedByRequireAuth(t *testing.T) {
	handler := RequireAuth(RequireAdmin(testHandler()))

	req := httptest.NewRequest("GET", "/admin/dashboard", nil)
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)

	if w.Code != http.StatusUnauthorized {
		t.Errorf("expected 401 with no token, got %d", w.Code)
	}
}
