package api

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

	"github.com/stretchr/testify/assert"
	"github.com/tidwall/gjson"

	"github.com/photoprism/photoprism/internal/config"
	"github.com/photoprism/photoprism/internal/service/cluster"
	"github.com/photoprism/photoprism/pkg/authn"
	"github.com/photoprism/photoprism/pkg/http/header"
)

func TestClusterPermissions(t *testing.T) {
	t.Run("UnauthorizedWhenPublicDisabled", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.Options().NodeRole = cluster.RolePortal

		// Disable public mode so Auth requires a session.
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)

		ClusterSummary(router)
		ClusterMetrics(router)

		r := PerformRequest(app, http.MethodGet, "/api/v1/cluster")
		assert.Equal(t, http.StatusUnauthorized, r.Code)

		r = PerformRequest(app, http.MethodGet, "/api/v1/cluster/metrics")
		assert.Equal(t, http.StatusUnauthorized, r.Code)
	})
	t.Run("ForbiddenFromCDN", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.Options().NodeRole = cluster.RolePortal

		ClusterListNodes(router)
		ClusterMetrics(router)

		req, _ := http.NewRequest(http.MethodGet, "/api/v1/cluster/nodes", nil)
		// Mark as CDN request, which Auth() forbids.
		req.Header.Set("Cdn-Host", "edge.example")
		w := httptest.NewRecorder()
		app.ServeHTTP(w, req)
		assert.Equal(t, http.StatusForbidden, w.Code)
	})
	t.Run("AdminCanAccess", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.Options().NodeRole = cluster.RolePortal
		ClusterSummary(router)
		ClusterMetrics(router)
		token := AuthenticateAdmin(app, router)
		r := AuthenticatedRequest(app, http.MethodGet, "/api/v1/cluster", token)
		assert.Equal(t, http.StatusOK, r.Code)

		r = AuthenticatedRequest(app, http.MethodGet, "/api/v1/cluster/metrics", token)
		assert.Equal(t, http.StatusOK, r.Code)
	})

	// Note: most fixture users have admin role; client-scope test below covers non-admin denial.

	t.Run("ClientInsufficientScope", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.Options().NodeRole = cluster.RolePortal
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)

		// Acquire client token with metrics scope (does not include cluster).
		OAuthToken(router)

		data := url.Values{
			"grant_type":    {authn.GrantClientCredentials.String()},
			"client_id":     {"cs5cpu17n6gj2qo5"},
			"client_secret": {"xcCbOrw6I0vcoXzhnOmXhjpVSyFq0l0e"},
			"scope":         {"metrics"},
		}
		req, _ := http.NewRequest(http.MethodPost, "/api/v1/oauth/token", strings.NewReader(data.Encode()))
		req.Header.Add(header.ContentType, header.ContentTypeForm)
		w := httptest.NewRecorder()
		app.ServeHTTP(w, req)
		assert.Equal(t, http.StatusOK, w.Code)
		token := gjson.Get(w.Body.String(), "access_token").String()

		ClusterSummary(router)
		ClusterMetrics(router)
		r := AuthenticatedRequest(app, http.MethodGet, "/api/v1/cluster", token)
		assert.Equal(t, http.StatusForbidden, r.Code)

		r = AuthenticatedRequest(app, http.MethodGet, "/api/v1/cluster/metrics", token)
		assert.Equal(t, http.StatusForbidden, r.Code)
	})
}
