package handler

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

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"security-center-backend/internal/middleware"
	"security-center-backend/internal/service/jwt"
)

// stubSubmitAuth builds current_user claims from test headers, mimicking the
// JWT auth middleware (human: roles/permissions) and AgentAuth (agent Key:
// claims.Permissions = intersected Key scope, ActorType = "agent").
func stubSubmitAuth(c *gin.Context) {
	claims := &jwt.Claims{
		UserID:      1,
		Email:       "submit-perm-test@example.com",
		Permissions: splitSubmitTestPermissions(c.GetHeader("X-Test-Permissions")),
	}
	if c.GetHeader("X-Test-Actor") == "agent" {
		claims.ActorType = "agent"
		claims.AgentID = 7
		claims.DelegatedUserID = claims.UserID
	}
	c.Set("current_user", claims)
	c.Next()
}

func splitSubmitTestPermissions(raw string) []string {
	if raw == "" {
		return nil
	}
	parts := strings.Split(raw, ",")
	permissions := make([]string, 0, len(parts))
	for _, part := range parts {
		if permission := strings.TrimSpace(part); permission != "" {
			permissions = append(permissions, permission)
		}
	}
	return permissions
}

func newSubmitPermissionEngine() *gin.Engine {
	gin.SetMode(gin.TestMode)
	engine := gin.New()
	protected := engine.Group("/api/v1")
	protected.Use(stubSubmitAuth)
	module := &Module{Vulnerabilities: &Handler{}}
	module.RegisterRoutes(protected, middleware.Permission)
	return engine
}

// TestCreateRequiresSubmitFindingsPermission covers the four middleware-layer
// forms: human ± vuln:submit_findings, agent Key ± vuln:submit_findings scope.
// Penetrating the permission middleware lands in Create with an empty body,
// which fails JSON binding -> response code 400; rejection is code 403
// "insufficient permissions" (response.Error keeps HTTP 200, code in body).
func TestCreateRequiresSubmitFindingsPermission(t *testing.T) {
	engine := newSubmitPermissionEngine()

	tests := []struct {
		name        string
		actor       string
		permissions string
		wantCode    int
		wantMessage string
	}{
		{
			name:        "human with submit_findings permission passes",
			permissions: "vuln:view_findings,vuln:submit_findings",
			wantCode:    http.StatusBadRequest,
		},
		{
			name:        "human with view_findings only is forbidden",
			permissions: "vuln:view_findings",
			wantCode:    http.StatusForbidden,
			wantMessage: "insufficient permissions",
		},
		{
			name:        "agent key with submit_findings scope passes",
			actor:       "agent",
			permissions: "vuln:view_findings,vuln:submit_findings",
			wantCode:    http.StatusBadRequest,
		},
		{
			name:        "agent key without submit_findings scope is forbidden",
			actor:       "agent",
			permissions: "vuln:view_findings",
			wantCode:    http.StatusForbidden,
			wantMessage: "insufficient permissions",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodPost, "/api/v1/vulnerabilities", nil)
			req.Header.Set("X-Test-Permissions", tt.permissions)
			if tt.actor != "" {
				req.Header.Set("X-Test-Actor", tt.actor)
			}
			w := httptest.NewRecorder()

			engine.ServeHTTP(w, req)

			require.Equal(t, http.StatusOK, w.Code, "body=%s", w.Body.String())
			var body struct {
				Code    int    `json:"code"`
				Message string `json:"message"`
			}
			require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body), "body=%s", w.Body.String())
			require.Equal(t, tt.wantCode, body.Code, "body=%s", w.Body.String())
			if tt.wantMessage != "" {
				require.Equal(t, tt.wantMessage, body.Message, "body=%s", w.Body.String())
			}
		})
	}
}

// TestModuleRegistersCreateExactlyOnceOnSubmitGroup guards the route split:
// POST /vulnerabilities must be registered exactly once (on the
// vuln:submit_findings subgroup), and the other restricted routes must stay
// registered (on the vuln:view_findings group).
func TestModuleRegistersCreateExactlyOnceOnSubmitGroup(t *testing.T) {
	engine := newSubmitPermissionEngine()

	createCount := 0
	registered := make(map[string]bool)
	for _, route := range engine.Routes() {
		key := route.Method + " " + route.Path
		registered[key] = true
		if key == "POST /api/v1/vulnerabilities" {
			createCount++
		}
	}

	require.Equal(t, 1, createCount, "POST /api/v1/vulnerabilities must be registered exactly once")

	// Representative sample of the 15 restricted routes that must remain.
	for _, key := range []string{
		"GET /api/v1/vulnerabilities/config",
		"POST /api/v1/vulnerabilities/config",
		"POST /api/v1/vulnerabilities/uploads",
		"POST /api/v1/vulnerabilities/uploads/confirm",
		"GET /api/v1/vulnerabilities/uploads/:id/content",
		"GET /api/v1/vulnerabilities/uploads/:id/url",
		"PUT /api/v1/vulnerabilities/:public_id",
		"GET /api/v1/vulnerabilities/:public_id/events",
		"POST /api/v1/vulnerabilities/:public_id/transition",
		"POST /api/v1/vulnerabilities/:public_id/meta",
		"POST /api/v1/vulnerabilities/:public_id/viewers",
		"GET /api/v1/vulnerabilities/:public_id/comments",
		"POST /api/v1/vulnerabilities/:public_id/comments",
		"POST /api/v1/vulnerabilities/:public_id/claim",
		"POST /api/v1/vulnerabilities/:public_id/assign",
	} {
		require.True(t, registered[key], "route %s must stay registered", key)
	}
}
