package mcp

import (
	"context"
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
	mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"

	"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"
	"github.com/yan1h/agent-coding-workflow/internal/user"
)

// MountTransports 在 chi router 上挂载 MCP transport 端点：
//   - POST/GET /mcp  Streamable HTTP（2025-03-26+，主推）
//   - POST/GET /mcp/sse  legacy HTTP+SSE（2024-11-05，SSE handler 自行根据 ?sessionid 分派）
//
// 中间件链：Auth → RateLimit → injectTokenID → SDK handler。
func MountTransports(
	r chi.Router,
	srv *mcpsdk.Server,
	ts TokenService,
	rl *RateLimiter,
) {
	streamable := mcpsdk.NewStreamableHTTPHandler(func(*http.Request) *mcpsdk.Server {
		return srv
	}, nil)
	sse := mcpsdk.NewSSEHandler(func(*http.Request) *mcpsdk.Server {
		return srv
	}, nil)

	// 嵌套 group 让中间件作用范围限定到 /mcp/* 子树
	r.Group(func(r chi.Router) {
		r.Use(NewAuthMiddleware(ts))
		r.Use(NewRateLimitMiddleware(rl))
		r.Use(injectTokenIDMiddleware())

		// Streamable HTTP
		r.Method(http.MethodPost, "/mcp", streamable)
		r.Method(http.MethodGet, "/mcp", streamable)

		// Legacy HTTP+SSE（SSE handler 自行分派 GET=stream / POST=message via ?sessionid）
		r.Method(http.MethodGet, "/mcp/sse", sse)
		r.Method(http.MethodPost, "/mcp/sse", sse)
	})
}

// injectTokenIDMiddleware 在 auth middleware 之后跑，把 AuthSession.TokenID
// 复制到 ctx 的 mcpTokenIDCtxKey，供 AuditWrap 在 audit metadata 里加 via_mcp 标记。
func injectTokenIDMiddleware() func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			sess, ok := FromContext(r.Context())
			if !ok {
				next.ServeHTTP(w, r)
				return
			}
			ctx := withMcpTokenID(r.Context(), sess.TokenID)
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

// ===== Admin Token CRUD HTTP Handler =====

// AdminHandlerDeps 装配 admin token CRUD HTTP handler 所需依赖。
type AdminHandlerDeps struct {
	Tokens TokenService
	Users  user.Service
}

// MountAdmin 把 admin token CRUD 挂到 /api/v1/mcp/tokens。
func MountAdmin(r chi.Router, deps AdminHandlerDeps) {
	r.Route("/api/v1/mcp/tokens", func(r chi.Router) {
		r.Post("/", postToken(deps))
		r.Get("/", listTokens(deps))
		r.Delete("/{id}", deleteToken(deps))
	})
}

type postTokenReq struct {
	UserID    string    `json:"user_id,omitempty"`
	Name      string    `json:"name"`
	Scope     Scope     `json:"scope"`
	ExpiresAt time.Time `json:"expires_at"`
}
type postTokenResp struct {
	ID        string    `json:"id"`
	Token     string    `json:"token"`
	ExpiresAt time.Time `json:"expires_at"`
}

func postToken(deps AdminHandlerDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		rid := middleware.RequestIDFromContext(r.Context())
		caller, isAdmin, err := resolveSessionUser(r.Context(), deps.Users, r)
		if err != nil {
			httpx.WriteError(w, rid, err)
			return
		}
		if !isAdmin {
			httpx.WriteError(w, rid, errs.New(errs.CodeForbidden, "admin only"))
			return
		}
		var req postTokenReq
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			httpx.WriteError(w, rid, errs.Wrap(err, errs.CodeInvalidInput, "decode body"))
			return
		}
		var targetUID uuid.UUID
		if req.UserID == "" {
			targetUID = caller
		} else {
			tid, err := uuid.Parse(req.UserID)
			if err != nil {
				httpx.WriteError(w, rid, errs.Wrap(err, errs.CodeInvalidInput, "user_id parse"))
				return
			}
			targetUID = tid
		}
		res, err := deps.Tokens.IssueAdmin(r.Context(), IssueAdminInput{
			UserID:    targetUID,
			Name:      req.Name,
			Scope:     req.Scope,
			ExpiresAt: req.ExpiresAt,
			CreatedBy: caller,
		})
		if err != nil {
			httpx.WriteError(w, rid, err)
			return
		}
		httpx.WriteJSON(w, http.StatusCreated, postTokenResp{
			ID:        res.ID.String(),
			Token:     res.Plaintext,
			ExpiresAt: derefTime(res.ExpiresAt),
		})
	}
}

type tokenRow struct {
	ID         string  `json:"id"`
	Name       string  `json:"name"`
	Issuer     string  `json:"issuer"`
	Scope      Scope   `json:"scope"`
	ExpiresAt  *string `json:"expires_at,omitempty"`
	RevokedAt  *string `json:"revoked_at,omitempty"`
	LastUsedAt *string `json:"last_used_at,omitempty"`
	CreatedAt  string  `json:"created_at"`
}

func listTokens(deps AdminHandlerDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		rid := middleware.RequestIDFromContext(r.Context())
		caller, isAdmin, err := resolveSessionUser(r.Context(), deps.Users, r)
		if err != nil {
			httpx.WriteError(w, rid, err)
			return
		}
		var callerFilter *uuid.UUID
		if !isAdmin {
			callerFilter = &caller
		}
		activeOnly := r.URL.Query().Get("active_only") == "true"
		tokens, err := deps.Tokens.List(r.Context(), callerFilter, activeOnly)
		if err != nil {
			httpx.WriteError(w, rid, err)
			return
		}
		out := make([]tokenRow, 0, len(tokens))
		for _, t := range tokens {
			row := tokenRow{
				ID: t.ID.String(), Name: t.Name,
				Issuer: string(t.Issuer), Scope: t.Scope,
				CreatedAt: t.CreatedAt.Format(time.RFC3339),
			}
			if t.ExpiresAt != nil {
				s := t.ExpiresAt.Format(time.RFC3339)
				row.ExpiresAt = &s
			}
			if t.RevokedAt != nil {
				s := t.RevokedAt.Format(time.RFC3339)
				row.RevokedAt = &s
			}
			if t.LastUsedAt != nil {
				s := t.LastUsedAt.Format(time.RFC3339)
				row.LastUsedAt = &s
			}
			out = append(out, row)
		}
		httpx.WriteJSON(w, http.StatusOK, out)
	}
}

func deleteToken(deps AdminHandlerDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		rid := middleware.RequestIDFromContext(r.Context())
		caller, isAdmin, err := resolveSessionUser(r.Context(), deps.Users, r)
		if err != nil {
			httpx.WriteError(w, rid, err)
			return
		}
		idStr := chi.URLParam(r, "id")
		id, err := uuid.Parse(idStr)
		if err != nil {
			httpx.WriteError(w, rid, errs.Wrap(err, errs.CodeInvalidInput, "id parse"))
			return
		}
		// 授权：仅 admin 或 token 持有者可撤销。非 admin 时按 id 反查 token
		// 校验归属；不归属（或不存在）一律返 NotFound，避免暴露 token 是否存在。
		if !isAdmin {
			tok, err := deps.Tokens.GetByID(r.Context(), id)
			if err != nil || tok.UserID != caller {
				httpx.WriteError(w, rid, errs.New(errs.CodeMcpTokenNotFound, "mcp token not found"))
				return
			}
		}
		if err := deps.Tokens.Revoke(r.Context(), id, caller); err != nil {
			httpx.WriteError(w, rid, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

func resolveSessionUser(ctx context.Context, us user.Service, r *http.Request) (uuid.UUID, bool, error) {
	tok := extractBearer(r)
	uid, ok, err := us.ResolveSession(ctx, tok)
	if err != nil {
		return uuid.Nil, false, errs.Wrap(err, errs.CodeInternal, "resolve session")
	}
	if !ok {
		return uuid.Nil, false, errs.New(errs.CodeUnauthorized, "session invalid")
	}
	u, err := us.Get(ctx, uid)
	if err != nil {
		return uuid.Nil, false, err
	}
	return uid, u.IsAdmin, nil
}

func extractBearer(r *http.Request) string {
	h := r.Header.Get("Authorization")
	if strings.HasPrefix(h, "Bearer ") {
		return strings.TrimPrefix(h, "Bearer ")
	}
	return ""
}

func derefTime(t *time.Time) time.Time {
	if t == nil {
		return time.Time{}
	}
	return *t
}
