package agent

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"log/slog"
	"net/http"
	"regexp"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"security-center-backend/internal/middleware"
	"security-center-backend/internal/model"
	"security-center-backend/internal/pkg/response"
)

// defaultKeyPrefix is the fallback plaintext prefix when AgentConfig.KeyPrefix
// is not configured.
const defaultKeyPrefix = "sk-agent-"

// nonAlnumRun matches any run of non-alphanumeric characters; each run
// collapses into a single "-" in the slug.
var nonAlnumRun = regexp.MustCompile(`[^a-z0-9]+`)

// Create handles POST /agent/api-keys (P2-T5, plan §2.3). It issues a new API
// key owned by the current user; the plaintext is returned exactly once in the
// response and only its sha256 hash is persisted.
func (h *Handler) Create(c *gin.Context) {
	var req CreateAPIKeyRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.Error(c, http.StatusBadRequest, "invalid request body: "+err.Error())
		return
	}
	claims := currentUser(c)
	if claims == nil {
		response.Error(c, http.StatusUnauthorized, "not authenticated")
		return
	}

	// 纵深防御: 申请的 scope 必须同时落在 agent 白名单内且是当前用户已有的权限。
	if !middleware.ScopeSubsetOfUserAndAgentAllowed(req.PermissionScope, claims.Permissions) {
		slog.Warn("create api key rejected: scope outside agent allowlist or exceeds owner permissions",
			"user_id", claims.UserID, "agent_name", req.AgentName, "scopes", req.PermissionScope)
		response.Error(c, http.StatusForbidden, "scope exceeds your permissions or not allowed for agent")
		return
	}

	if !h.cfg.AllowPermanentKey && req.ExpiresAt == nil {
		response.Error(c, http.StatusBadRequest, "expires_at is required")
		return
	}
	if req.ExpiresAt != nil && !req.ExpiresAt.After(time.Now()) {
		response.Error(c, http.StatusBadRequest, "expires_at must be in the future")
		return
	}

	plaintext, err := h.generatePlaintextKey(req.AgentName)
	if err != nil {
		response.Error(c, http.StatusInternalServerError, "failed to generate api key")
		return
	}

	key := &model.APIKey{
		KeyHash:         sha256Hex(plaintext),
		KeyPrefix:       plaintext[:16],
		AgentName:       req.AgentName,
		OwnerUserID:     claims.UserID, // 强制归属当前用户, 请求体里的任何 owner 字段一律忽略
		PermissionScope: model.StringArray(req.PermissionScope),
		Status:          model.APIKeyStatusActive,
		ExpiresAt:       req.ExpiresAt,
	}
	if err := h.repo.Create(c.Request.Context(), key); err != nil {
		response.Error(c, http.StatusInternalServerError, "failed to create api key")
		return
	}

	response.Success(c, &CreateAPIKeyResponse{
		APIKeyResponse: *toAPIKeyResponse(key),
		APIKey:         plaintext,
	})
}

// generatePlaintextKey builds "<prefix><slug(agentName)>-<32 hex chars>", e.g.
// "sk-agent-my-bot-9f2c...". The random part carries 16 bytes of entropy.
func (h *Handler) generatePlaintextKey(agentName string) (string, error) {
	prefix := h.cfg.KeyPrefix
	if prefix == "" {
		prefix = defaultKeyPrefix
	}
	random := make([]byte, 16)
	if _, err := rand.Read(random); err != nil {
		return "", fmt.Errorf("generate random key material: %w", err)
	}
	return prefix + slugAgentName(agentName) + "-" + hex.EncodeToString(random), nil
}

// slugAgentName normalizes an agent name for embedding into the plaintext key:
// lowercase, non-alphanumeric runs become "-", trimmed, capped at 20 chars,
// falling back to "agent" when nothing usable remains.
func slugAgentName(name string) string {
	slug := nonAlnumRun.ReplaceAllString(strings.ToLower(name), "-")
	slug = strings.Trim(slug, "-")
	if slug == "" {
		return "agent"
	}
	if len(slug) > 20 {
		slug = strings.TrimRight(slug[:20], "-")
	}
	return slug
}

// sha256Hex 返回输入的 sha256 hex 摘要, 与落库/鉴权侧的哈希口径一致。
func sha256Hex(raw string) string {
	sum := sha256.Sum256([]byte(raw))
	return fmt.Sprintf("%x", sum)
}
