package middleware

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

	"github.com/gin-gonic/gin"

	"security-center-backend/internal/config"
	"security-center-backend/internal/model"
	"security-center-backend/internal/repository"
	"security-center-backend/internal/service/jwt"
)

// setupAgentContextRouter 在下游 handler 里从请求上下文读取 agent claims 并捕获。
func setupAgentContextRouter(repo repository.APIKeyRepository, cfg config.AgentConfig, got **jwt.Claims, gotOK *bool) *gin.Engine {
	r := gin.New()
	r.Use(AgentAuth(repo, cfg))
	r.GET("/", func(c *gin.Context) {
		*got, *gotOK = AgentClaimsFromContext(c.Request.Context())
		c.Status(http.StatusNoContent)
	})
	return r
}

func TestAgentClaimsFromContext(t *testing.T) {
	future := time.Now().Add(time.Hour)
	activeKey := &model.APIKey{
		Base:            model.Base{ID: 7},
		AgentName:       "sec-bot",
		OwnerUserID:     42,
		Owner:           &model.User{Base: model.Base{ID: 42}, Status: model.UserStatusActive},
		PermissionScope: model.StringArray{"vuln:view_findings", "vuln:close_findings"},
		Status:          model.APIKeyStatusActive,
		ExpiresAt:       &future,
	}
	enabled := config.AgentConfig{Enabled: true}

	tests := []struct {
		name         string
		apiKeyHeader string // 空表示不设置 X-API-Key 头 (人类 JWT 请求)
		repo         *agentAuthFakeRepo
		wantOK       bool
		checkClaims  func(t *testing.T, claims *jwt.Claims)
	}{
		{
			name:         "agent 请求上下文可取回 claims 且字段完整",
			apiKeyHeader: "sk-agent-xxx",
			repo:         &agentAuthFakeRepo{rec: activeKey},
			wantOK:       true,
			checkClaims: func(t *testing.T, claims *jwt.Claims) {
				t.Helper()
				if claims.ActorType != "agent" {
					t.Errorf("ActorType = %q, want %q", claims.ActorType, "agent")
				}
				if claims.AgentID != activeKey.ID {
					t.Errorf("AgentID = %d, want %d", claims.AgentID, activeKey.ID)
				}
				if claims.AgentName != activeKey.AgentName {
					t.Errorf("AgentName = %q, want %q", claims.AgentName, activeKey.AgentName)
				}
				if claims.DelegatedUserID != activeKey.OwnerUserID {
					t.Errorf("DelegatedUserID = %d, want %d", claims.DelegatedUserID, activeKey.OwnerUserID)
				}
				if claims.UserID != activeKey.OwnerUserID {
					t.Errorf("UserID = %d, want OwnerUserID %d", claims.UserID, activeKey.OwnerUserID)
				}
				if !reflect.DeepEqual(claims.Permissions, []string(activeKey.PermissionScope)) {
					t.Errorf("Permissions = %v, want PermissionScope %v", claims.Permissions, activeKey.PermissionScope)
				}
			},
		},
		{
			name:   "人类请求 (无 X-API-Key 头) 上下文无 claims",
			repo:   &agentAuthFakeRepo{rec: activeKey},
			wantOK: false,
		},
		{
			name:         "agent 认证失败不注入",
			apiKeyHeader: "sk-agent-unknown",
			repo:         &agentAuthFakeRepo{}, // FindByHash 返回 (nil, nil)
			wantOK:       false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			resetTouchState(t)
			var got *jwt.Claims
			gotOK := false
			r := setupAgentContextRouter(tt.repo, enabled, &got, &gotOK)
			w := httptest.NewRecorder()
			req := httptest.NewRequest(http.MethodGet, "/", nil)
			if tt.apiKeyHeader != "" {
				req.Header.Set("X-API-Key", tt.apiKeyHeader)
			}
			r.ServeHTTP(w, req)

			if gotOK != tt.wantOK {
				t.Errorf("AgentClaimsFromContext ok = %v, want %v", gotOK, tt.wantOK)
			}
			if !tt.wantOK && got != nil {
				t.Errorf("不应取回 claims, got %+v", got)
			}
			if tt.checkClaims != nil {
				if got == nil {
					t.Fatal("应取回 agent claims")
				}
				tt.checkClaims(t, got)
			}
		})
	}
}

// TestAgentClaimsContextHelper 纯 helper 行为: 空上下文返回 (nil, false); 注入 nil 视同未注入。
func TestAgentClaimsContextHelper(t *testing.T) {
	if claims, ok := AgentClaimsFromContext(context.Background()); ok || claims != nil {
		t.Errorf("空上下文应返回 (nil, false), got (%+v, %v)", claims, ok)
	}

	injected := &jwt.Claims{ActorType: "agent", AgentID: 9, AgentName: "helper-bot", DelegatedUserID: 3}
	ctx := WithAgentClaims(context.Background(), injected)
	claims, ok := AgentClaimsFromContext(ctx)
	if !ok || claims != injected {
		t.Errorf("注入后应原样取回, got (%+v, %v)", claims, ok)
	}

	nilCtx := WithAgentClaims(context.Background(), nil)
	if claims, ok := AgentClaimsFromContext(nilCtx); ok || claims != nil {
		t.Errorf("注入 nil 应视同未注入, got (%+v, %v)", claims, ok)
	}
}
