package auth

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

	"github.com/google/uuid"
)

// fakeAdminLookup implements AdminLookup for unit tests.
type fakeAdminLookup struct {
	admin bool
	err   error
}

func (f fakeAdminLookup) IsAdmin(ctx context.Context, id uuid.UUID) (bool, error) {
	return f.admin, f.err
}

// withUID returns a request that already has the user-id context value set,
// matching what Client.WithUserID would have populated.
func withUID(req *http.Request, id uuid.UUID) *http.Request {
	ctx := context.WithValue(req.Context(), userIDContextKey, id)
	return req.WithContext(ctx)
}

func TestRequireAdmin_AllowsAdmin(t *testing.T) {
	called := false
	next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		w.WriteHeader(http.StatusOK)
	})
	h := RequireAdmin(fakeAdminLookup{admin: true})(next)

	req := withUID(httptest.NewRequest("GET", "/api/admin/users", nil), uuid.New())
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)

	if !called {
		t.Fatal("admin user should reach the wrapped handler")
	}
	if rec.Code != http.StatusOK {
		t.Errorf("status: got %d, want 200", rec.Code)
	}
}

func TestRequireAdmin_RejectsNonAdminAPI(t *testing.T) {
	next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Fatal("non-admin must not reach the wrapped handler")
	})
	h := RequireAdmin(fakeAdminLookup{admin: false})(next)

	req := withUID(httptest.NewRequest("GET", "/api/admin/users", nil), uuid.New())
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)

	if rec.Code != http.StatusForbidden {
		t.Errorf("API path should 403 for non-admin, got %d", rec.Code)
	}
}

func TestRequireAdmin_RedirectsNonAdminBrowser(t *testing.T) {
	next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Fatal("non-admin must not reach the wrapped handler")
	})
	h := RequireAdmin(fakeAdminLookup{admin: false})(next)

	req := withUID(httptest.NewRequest("GET", "/admin/team", nil), uuid.New())
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)

	if rec.Code != http.StatusFound {
		t.Errorf("browser path should 302 for non-admin, got %d", rec.Code)
	}
	if got := rec.Header().Get("Location"); got != "/dashboard?forbidden=admin" {
		t.Errorf("redirect target: got %q", got)
	}
}

func TestRequireAdmin_RejectsUnauthenticated(t *testing.T) {
	next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Fatal("unauthenticated must not reach the wrapped handler")
	})
	h := RequireAdmin(fakeAdminLookup{admin: true})(next)

	// No userIDContextKey on the request — simulates a path that didn't
	// authenticate first. The middleware must fail closed even when the
	// lookup would have approved.
	req := httptest.NewRequest("GET", "/api/admin/users", nil)
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)

	if rec.Code != http.StatusUnauthorized {
		t.Errorf("missing user id should 401, got %d", rec.Code)
	}
}

func TestRequireAdmin_FailsClosedOnLookupError(t *testing.T) {
	next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Fatal("lookup-error path must not reach the wrapped handler")
	})
	h := RequireAdmin(fakeAdminLookup{err: errors.New("db gone")})(next)

	req := withUID(httptest.NewRequest("GET", "/api/admin/users", nil), uuid.New())
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)

	if rec.Code != http.StatusInternalServerError {
		t.Errorf("lookup error should 500 (fail closed), got %d", rec.Code)
	}
}
