package middleware

import (
	"context"
	"encoding/json"
	"errors"
	"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/pkg/response"
	"security-center-backend/internal/repository"
	"security-center-backend/internal/service/jwt"
)

// agentAuthFakeRepo 实现 APIKeyRepository 全 8 方法, 仅 FindByHash 可配。
type agentAuthFakeRepo struct {
	rec     *model.APIKey
	findErr error
}

var _ repository.APIKeyRepository = (*agentAuthFakeRepo)(nil)

func (f *agentAuthFakeRepo) Create(_ context.Context, _ *model.APIKey) error { return nil }

func (f *agentAuthFakeRepo) FindByHash(_ context.Context, _ string) (*model.APIKey, error) {
	return f.rec, f.findErr
}

func (f *agentAuthFakeRepo) TouchLastUsed(_ context.Context, _ uint64) error { return nil }

func (f *agentAuthFakeRepo) FindByIDForOwner(_ context.Context, _, _ uint64) (*model.APIKey, error) {
	return nil, nil
}

func (f *agentAuthFakeRepo) ListByOwner(_ context.Context, _ uint64) ([]*model.APIKey, error) {
	return nil, nil
}

func (f *agentAuthFakeRepo) Revoke(_ context.Context, _, _ uint64) error { return nil }

func (f *agentAuthFakeRepo) FindByID(_ context.Context, _ uint64) (*model.APIKey, error) {
	return nil, nil
}

func (f *agentAuthFakeRepo) ListAll(_ context.Context, _ repository.ListAPIKeysFilter) ([]*model.APIKey, error) {
	return nil, nil
}

// agentAuthCapture 记录下游 handler 观察到的上下文注入结果。
type agentAuthCapture struct {
	downstream bool
	claims     *jwt.Claims
	authMethod string
	apiKeyID   uint64
}

func setupAgentAuthRouter(repo repository.APIKeyRepository, cfg config.AgentConfig, got *agentAuthCapture) *gin.Engine {
	r := gin.New()
	r.Use(AgentAuth(repo, cfg))
	r.GET("/", func(c *gin.Context) {
		got.downstream = true
		if v, ok := c.Get("current_user"); ok {
			got.claims, _ = v.(*jwt.Claims)
		}
		if v, ok := c.Get("auth_method"); ok {
			got.authMethod, _ = v.(string)
		}
		if v, ok := c.Get("api_key_id"); ok {
			got.apiKeyID, _ = v.(uint64)
		}
		c.Status(http.StatusNoContent)
	})
	return r
}

func TestAgentAuth(t *testing.T) {
	future := time.Now().Add(time.Hour)
	past := time.Now().Add(-time.Hour)

	activeOwner := &model.User{Base: model.Base{ID: 42}, Status: model.UserStatusActive}

	activeKey := &model.APIKey{
		Base:            model.Base{ID: 7},
		AgentName:       "sec-bot",
		OwnerUserID:     42,
		Owner:           activeOwner,
		PermissionScope: model.StringArray{"caasm:view_assets", "vuln:view_list"},
		Status:          model.APIKeyStatusActive,
		ExpiresAt:       &future,
	}
	enabled := config.AgentConfig{Enabled: true}

	tests := []struct {
		name           string
		apiKeyHeader   string // 空表示不设置 X-API-Key 头
		cfg            config.AgentConfig
		repo           *agentAuthFakeRepo
		wantDownstream bool
		wantCode       int    // 仅错误分支断言 body
		wantMessage    string // 空表示不断言 body
		checkContext   func(t *testing.T, got *agentAuthCapture)
	}{
		{
			name:           "无 X-API-Key 头直通",
			cfg:            enabled,
			repo:           &agentAuthFakeRepo{},
			wantDownstream: true,
			checkContext: func(t *testing.T, got *agentAuthCapture) {
				t.Helper()
				if got.claims != nil {
					t.Errorf("不应注入 claims, got %+v", got.claims)
				}
				if got.authMethod != "" {
					t.Errorf("不应注入 auth_method, got %q", got.authMethod)
				}
			},
		},
		{
			name:         "agent 功能关闭",
			apiKeyHeader: "sk-agent-xxx",
			cfg:          config.AgentConfig{Enabled: false},
			repo:         &agentAuthFakeRepo{rec: activeKey},
			wantCode:     http.StatusForbidden,
			wantMessage:  "agent access disabled",
		},
		{
			name:         "key 不存在",
			apiKeyHeader: "sk-agent-xxx",
			cfg:          enabled,
			repo:         &agentAuthFakeRepo{}, // FindByHash 返回 (nil, nil)
			wantCode:     http.StatusUnauthorized,
			wantMessage:  "invalid api key",
		},
		{
			name:         "repo 查询错误按 invalid api key 处理",
			apiKeyHeader: "sk-agent-xxx",
			cfg:          enabled,
			repo:         &agentAuthFakeRepo{findErr: errors.New("db down")},
			wantCode:     http.StatusUnauthorized,
			wantMessage:  "invalid api key",
		},
		{
			name:         "key 已吊销",
			apiKeyHeader: "sk-agent-xxx",
			cfg:          enabled,
			repo: &agentAuthFakeRepo{rec: &model.APIKey{
				Base:            model.Base{ID: 7},
				AgentName:       "sec-bot",
				OwnerUserID:     42,
				Owner:           activeOwner,
				PermissionScope: model.StringArray{"caasm:view_assets"},
				Status:          model.APIKeyStatusRevoked,
			}},
			wantCode:    http.StatusUnauthorized,
			wantMessage: "api key revoked",
		},
		{
			name:         "key 已过期",
			apiKeyHeader: "sk-agent-xxx",
			cfg:          enabled,
			repo: &agentAuthFakeRepo{rec: &model.APIKey{
				Base:            model.Base{ID: 7},
				AgentName:       "sec-bot",
				OwnerUserID:     42,
				Owner:           activeOwner,
				PermissionScope: model.StringArray{"caasm:view_assets"},
				Status:          model.APIKeyStatusActive,
				ExpiresAt:       &past,
			}},
			wantCode:    http.StatusUnauthorized,
			wantMessage: "api key expired",
		},
		{
			name:         "scope 含写权限被拒绝",
			apiKeyHeader: "sk-agent-xxx",
			cfg:          enabled,
			repo: &agentAuthFakeRepo{rec: &model.APIKey{
				Base:            model.Base{ID: 7},
				AgentName:       "sec-bot",
				OwnerUserID:     42,
				Owner:           activeOwner,
				PermissionScope: model.StringArray{"caasm:view_assets", "caasm:manage_assets"},
				Status:          model.APIKeyStatusActive,
				ExpiresAt:       &future,
			}},
			wantCode:    http.StatusForbidden,
			wantMessage: "agent scope not allowed",
		},
		{
			name:         "主人 inactive 拒绝",
			apiKeyHeader: "sk-agent-xxx",
			cfg:          enabled,
			repo: &agentAuthFakeRepo{rec: &model.APIKey{
				Base:            model.Base{ID: 7},
				AgentName:       "sec-bot",
				OwnerUserID:     42,
				Owner:           &model.User{Base: model.Base{ID: 42}, Status: model.UserStatusInactive},
				PermissionScope: model.StringArray{"caasm:view_assets"},
				Status:          model.APIKeyStatusActive,
				ExpiresAt:       &future,
			}},
			wantCode:    http.StatusUnauthorized,
			wantMessage: "api key owner disabled",
		},
		{
			name:         "主人 suspended 拒绝",
			apiKeyHeader: "sk-agent-xxx",
			cfg:          enabled,
			repo: &agentAuthFakeRepo{rec: &model.APIKey{
				Base:            model.Base{ID: 7},
				AgentName:       "sec-bot",
				OwnerUserID:     42,
				Owner:           &model.User{Base: model.Base{ID: 42}, Status: model.UserStatusSuspended},
				PermissionScope: model.StringArray{"caasm:view_assets"},
				Status:          model.APIKeyStatusActive,
				ExpiresAt:       &future,
			}},
			wantCode:    http.StatusUnauthorized,
			wantMessage: "api key owner disabled",
		},
		{
			name:         "Owner 缺失(软删/孤儿 Key)拒绝",
			apiKeyHeader: "sk-agent-xxx",
			cfg:          enabled,
			repo: &agentAuthFakeRepo{rec: &model.APIKey{
				Base:            model.Base{ID: 7},
				AgentName:       "sec-bot",
				OwnerUserID:     42,
				Owner:           nil,
				PermissionScope: model.StringArray{"caasm:view_assets"},
				Status:          model.APIKeyStatusActive,
				ExpiresAt:       &future,
			}},
			wantCode:    http.StatusUnauthorized,
			wantMessage: "api key owner disabled",
		},
		{
			name:           "验证成功注入 claims",
			apiKeyHeader:   "sk-agent-xxx",
			cfg:            enabled,
			repo:           &agentAuthFakeRepo{rec: activeKey},
			wantDownstream: true,
			checkContext: func(t *testing.T, got *agentAuthCapture) {
				t.Helper()
				if got.claims == nil {
					t.Fatal("应注入 current_user claims")
				}
				if got.claims.ActorType != "agent" {
					t.Errorf("ActorType = %q, want %q", got.claims.ActorType, "agent")
				}
				if got.claims.AgentID != activeKey.ID {
					t.Errorf("AgentID = %d, want %d", got.claims.AgentID, activeKey.ID)
				}
				if got.claims.AgentName != activeKey.AgentName {
					t.Errorf("AgentName = %q, want %q", got.claims.AgentName, activeKey.AgentName)
				}
				if got.claims.DelegatedUserID != activeKey.OwnerUserID {
					t.Errorf("DelegatedUserID = %d, want %d", got.claims.DelegatedUserID, activeKey.OwnerUserID)
				}
				if got.claims.UserID != activeKey.OwnerUserID {
					t.Errorf("UserID = %d, want OwnerUserID %d", got.claims.UserID, activeKey.OwnerUserID)
				}
				if !reflect.DeepEqual(got.claims.Permissions, []string(activeKey.PermissionScope)) {
					t.Errorf("Permissions = %v, want %v", got.claims.Permissions, activeKey.PermissionScope)
				}
				if got.authMethod != "agent" {
					t.Errorf("auth_method = %q, want %q", got.authMethod, "agent")
				}
				if got.apiKeyID != activeKey.ID {
					t.Errorf("api_key_id = %d, want %d", got.apiKeyID, activeKey.ID)
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			resetTouchState(t)
			got := &agentAuthCapture{}
			r := setupAgentAuthRouter(tt.repo, tt.cfg, got)
			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 got.downstream != tt.wantDownstream {
				t.Errorf("downstream 执行 = %v, want %v", got.downstream, tt.wantDownstream)
			}
			if tt.wantMessage != "" {
				// response.Error 恒返回 HTTP 200, 错误码在 body code 字段。
				var body response.APIResponse
				if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
					t.Fatalf("响应非 JSON: %v, body=%q", err, w.Body.String())
				}
				if body.Code != tt.wantCode {
					t.Errorf("body code = %d, want %d", body.Code, tt.wantCode)
				}
				if body.Message != tt.wantMessage {
					t.Errorf("body message = %q, want %q", body.Message, tt.wantMessage)
				}
			}
			if tt.checkContext != nil {
				tt.checkContext(t, got)
			}
		})
	}
}
