package mcp

import (
	"context"
	"net/http"
	"strings"

	"github.com/google/uuid"

	"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
	httpx "github.com/yan1h/agent-coding-workflow/internal/transport/http"
	"github.com/yan1h/agent-coding-workflow/internal/transport/http/middleware"
)

// authCtxKey 是 ctx 上注入鉴权结果的 key 类型。
type authCtxKey struct{}

// AuthContextKey 暴露给同包文件（auth_middleware / handler / tools）从 ctx 取值。
var AuthContextKey = authCtxKey{}

// AuthSession 是中间件解析后注入 ctx 的鉴权快照。
// 不直接暴露 *Token（隐藏 hash 字段，避免下游误用）。
type AuthSession struct {
	TokenID string // 用于审计 metadata 写入
	UserID  string // user_id 字符串形式（避免反复 uuid.UUID -> string 转换）
	Scope   Scope
	Issuer  Issuer // system token（agent 会话持有）受额外限制，见 create_acp_session
	// ACPSessionID 仅 system token 非 nil：标识持有该 token 的 agent 所在 acp session。
	// request_subtask 据此把调用方映射回其编排 step。
	ACPSessionID *uuid.UUID
}

// FromContext 从 ctx 取出 AuthSession，未鉴权时返 (nil, false)。
func FromContext(ctx context.Context) (*AuthSession, bool) {
	s, ok := ctx.Value(AuthContextKey).(*AuthSession)
	return s, ok
}

// NewAuthMiddleware 返回 chi 风格 middleware：
//  1. 从 Authorization: Bearer <token> 取 plaintext；fallback 到 ?token=
//  2. 调 svc.Authenticate 验证
//  3. 把 AuthSession 注入 ctx 后 next.ServeHTTP
//  4. 失败 → 调 httpx.WriteError 返 4xx
func NewAuthMiddleware(svc TokenService) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			plain := extractPlaintext(r)
			if plain == "" {
				httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()),
					errs.New(errs.CodeMcpTokenInvalid, "missing token"))
				return
			}
			tok, err := svc.Authenticate(r.Context(), plain)
			if err != nil {
				httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()), err)
				return
			}
			sess := &AuthSession{
				TokenID:      tok.ID.String(),
				UserID:       tok.UserID.String(),
				Scope:        tok.Scope,
				Issuer:       tok.Issuer,
				ACPSessionID: tok.ACPSessionID,
			}
			ctx := context.WithValue(r.Context(), AuthContextKey, sess)
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

// extractPlaintext 优先取 Authorization 头部 Bearer 后的 token；空时回落 ?token= 查询参数。
// 这与 ACP WS 鉴权对齐（spec §4.3）。
func extractPlaintext(r *http.Request) string {
	if h := r.Header.Get("Authorization"); h != "" {
		const prefix = "Bearer "
		if strings.HasPrefix(h, prefix) {
			return strings.TrimSpace(h[len(prefix):])
		}
	}
	return r.URL.Query().Get("token")
}
