// Package user 内的 handler.go 把 Service 暴露为 HTTP 端点：登录、读取
// 当前用户信息、登出。这一层只做协议适配——解析请求、调用 Service、
// 把结果编码成 JSON——业务逻辑全部下沉到 service.go 中。
//
// 设计取舍：
//   - 路由前缀 /api/v1/auth 写死在 Mount 内：版本号是契约的一部分，调用方
//     不需要也不应该自定义；如果未来要支持多版本并行，再在 Mount 之上做分发。
//   - /me 与 /logout 都挂在 middleware.Auth 之后；/login 必须裸跑，否则永远
//     无法首次拿到 token。
//   - Service 接口已经满足 middleware.SessionResolver（同名同签名方法），
//     因此直接把 svc 传进 Auth，无需写额外的适配器。
//   - bearerFrom 在本包重复了 middleware.extractBearer 的逻辑：后者是包私有
//     函数，不导出；与其为一个 8 行的字符串解析改动 middleware 的 API 表面，
//     不如在调用侧自己拆一遍 Authorization 头。
package user

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

	"github.com/go-chi/chi/v5"
	"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"
)

// bearerPrefix 是 RFC 6750 规定的 Authorization 方案前缀。
const bearerPrefix = "Bearer "

// Handler 把 user.Service 暴露为 chi 路由。字段保持小写避免外部直接构造，
// 调用方必须通过 NewHandler 注入实现。
type Handler struct {
	svc Service
}

// NewHandler 用给定的 Service 构造 Handler。返回 *Handler 以便 Mount 这种
// 仅需值接收者的方法可以直接调用，无需暴露接口扩展点（目前没有第二种实现）。
func NewHandler(svc Service) *Handler { return &Handler{svc: svc} }

// Mount 把 user 模块的所有 HTTP 端点挂到给定 router 的 /api/v1/auth 子树。
// 调用方负责在挂载之前装好 RequestID/Recover/Logger 等通用中间件。
func (h *Handler) Mount(r chi.Router) {
	auth := middleware.Auth(h.svc, middleware.AuthOptions{})

	r.Route("/api/v1/auth", func(r chi.Router) {
		r.Post("/login", h.login)

		// /me 与 /logout 必须经过 Auth 中间件：未带 token 直接 401，由中间件
		// 落地响应；带了 token 才会走到下面的处理函数。
		r.With(auth).Get("/me", h.me)
		r.With(auth).Post("/logout", h.logout)
		// 自助改密：需认证，校验旧密码后撤销全部会话。
		r.With(auth).Post("/change-password", h.changePassword)
	})

	// 管理员用户管理子树：全部经 Auth，handler 内部再用 requireAdmin 做 admin 鉴权。
	r.Route("/api/v1/admin/users", func(r chi.Router) {
		r.Use(auth)
		r.Get("/", h.listUsers)
		r.Post("/", h.createUser)
		r.Get("/{id}", h.getUser)
		r.Patch("/{id}", h.updateUser)
		r.Delete("/{id}", h.deleteUser)
		r.Patch("/{id}/role", h.setRole)
		r.Patch("/{id}/enabled", h.setEnabled)
		r.Post("/{id}/reset-password", h.resetPassword)
	})
}

// requireAdmin 解析当前认证用户并校验其为管理员。返回 actor 的 UserID。
func (h *Handler) requireAdmin(r *http.Request) (uuid.UUID, error) {
	uid, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		return uuid.Nil, errs.New(errs.CodeUnauthorized, "not authenticated")
	}
	u, err := h.svc.Get(r.Context(), uid)
	if err != nil {
		return uuid.Nil, err
	}
	if !u.IsAdmin {
		return uuid.Nil, errs.New(errs.CodeForbidden, "需要管理员权限")
	}
	return uid, nil
}

// loginReq 是 POST /api/v1/auth/login 的请求体形态。
type loginReq struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

// loginResp 是登录成功后的响应形态。token 仅此一次返回明文。
type loginResp struct {
	Token string  `json:"token"`
	User  userDTO `json:"user"`
}

// userDTO 是对外暴露的用户视图。刻意不直接序列化 *User，避免哈希、时间等
// 服务端字段意外随响应外泄。
type userDTO struct {
	ID          string `json:"id"`
	Email       string `json:"email"`
	DisplayName string `json:"display_name"`
	IsAdmin     bool   `json:"is_admin"`
}

// toUserDTO 把领域 User 转成响应视图。集中此处便于 /login 与 /me 共用。
func toUserDTO(u *User) userDTO {
	return userDTO{
		ID:          u.ID.String(),
		Email:       u.Email,
		DisplayName: u.DisplayName,
		IsAdmin:     u.IsAdmin,
	}
}

func (h *Handler) login(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())

	var req loginReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		httpx.WriteError(w, rid, errs.New(errs.CodeInvalidInput, "请求体无效"))
		return
	}
	if strings.TrimSpace(req.Email) == "" || req.Password == "" {
		httpx.WriteError(w, rid, errs.New(errs.CodeInvalidInput, "缺少邮箱或密码"))
		return
	}

	tok, u, err := h.svc.Login(r.Context(), req.Email, req.Password, clientIP(r))
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, loginResp{Token: tok, User: toUserDTO(u)})
}

func (h *Handler) me(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())

	// Auth 中间件已经保证 user_id 必然存在；ok=false 仅在 Optional 模式或
	// 缺失中间件时出现，这里都不应当发生，按内部错误处理避免静默返回 0 值。
	uid, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		httpx.WriteError(w, rid, errs.New(errs.CodeInternal, "missing auth context"))
		return
	}
	u, err := h.svc.Get(r.Context(), uid)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, toUserDTO(u))
}

func (h *Handler) logout(w http.ResponseWriter, r *http.Request) {
	// Service.Logout 对未知 token 也返回 nil，因此即便 token 已被并发清理，
	// 这里也能给客户端一个一致的 204 响应。
	if err := h.svc.Logout(r.Context(), bearerFrom(r)); err != nil {
		httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()), err)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// bearerFrom 解析 Authorization 头的 Bearer 部分并返回明文 token。
// 行为与 middleware.extractBearer 一致：缺头或前缀不匹配时返回空串。
func bearerFrom(r *http.Request) string {
	h := r.Header.Get("Authorization")
	if !strings.HasPrefix(h, bearerPrefix) {
		return ""
	}
	return strings.TrimSpace(h[len(bearerPrefix):])
}

// clientIP 从请求头提取最接近真实客户端的 IP。优先级：X-Forwarded-For 首段
// > X-Real-IP > r.RemoteAddr。
//
// **安全提示**：当前未实现 trusted proxy 名单，X-Forwarded-For/X-Real-IP 可
// 被任意客户端伪造。生产部署必须在反向代理层 strip 客户端送来的这些头，仅
// 由代理本身写入；否则审计 IP 不可信。该问题在引入 ingress 配置时一并处理。
func clientIP(r *http.Request) string {
	if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
		if comma := strings.IndexByte(xff, ','); comma > 0 {
			return strings.TrimSpace(xff[:comma])
		}
		return strings.TrimSpace(xff)
	}
	if xri := r.Header.Get("X-Real-IP"); xri != "" {
		return strings.TrimSpace(xri)
	}
	return r.RemoteAddr
}

// ===== 管理员用户管理 =====

// adminUserDTO 是管理后台的用户视图，比 userDTO 多 enabled 与时间戳，但同样
// 不暴露 password_hash。
type adminUserDTO struct {
	ID          string    `json:"id"`
	Email       string    `json:"email"`
	DisplayName string    `json:"display_name"`
	IsAdmin     bool      `json:"is_admin"`
	Enabled     bool      `json:"enabled"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

func toAdminUserDTO(u *User) adminUserDTO {
	return adminUserDTO{
		ID:          u.ID.String(),
		Email:       u.Email,
		DisplayName: u.DisplayName,
		IsAdmin:     u.IsAdmin,
		Enabled:     u.Enabled,
		CreatedAt:   u.CreatedAt,
		UpdatedAt:   u.UpdatedAt,
	}
}

// userIDParam 解析路由中的 {id} 为 uuid。
func userIDParam(r *http.Request) (uuid.UUID, error) {
	id, err := uuid.Parse(chi.URLParam(r, "id"))
	if err != nil {
		return uuid.Nil, errs.New(errs.CodeInvalidInput, "无效的用户 id")
	}
	return id, nil
}

func (h *Handler) listUsers(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())
	if _, err := h.requireAdmin(r); err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	users, err := h.svc.ListUsers(r.Context())
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	out := make([]adminUserDTO, 0, len(users))
	for _, u := range users {
		out = append(out, toAdminUserDTO(u))
	}
	httpx.WriteJSON(w, http.StatusOK, out)
}

type createUserReq struct {
	Email       string `json:"email"`
	DisplayName string `json:"display_name"`
	Password    string `json:"password"`
	IsAdmin     bool   `json:"is_admin"`
}

func (h *Handler) createUser(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())
	if _, err := h.requireAdmin(r); err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	var req createUserReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		httpx.WriteError(w, rid, errs.New(errs.CodeInvalidInput, "请求体无效"))
		return
	}
	email := strings.TrimSpace(req.Email)
	display := strings.TrimSpace(req.DisplayName)
	if email == "" || display == "" {
		httpx.WriteError(w, rid, errs.New(errs.CodeInvalidInput, "缺少邮箱或显示名"))
		return
	}
	u, err := h.svc.CreateUser(r.Context(), CreateUserInput{
		Email:       email,
		DisplayName: display,
		Password:    req.Password,
		IsAdmin:     req.IsAdmin,
	})
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	httpx.WriteJSON(w, http.StatusCreated, toAdminUserDTO(u))
}

func (h *Handler) getUser(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())
	if _, err := h.requireAdmin(r); err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	id, err := userIDParam(r)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	u, err := h.svc.Get(r.Context(), id)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, toAdminUserDTO(u))
}

type updateUserReq struct {
	DisplayName string `json:"display_name"`
}

func (h *Handler) updateUser(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())
	if _, err := h.requireAdmin(r); err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	id, err := userIDParam(r)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	var req updateUserReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		httpx.WriteError(w, rid, errs.New(errs.CodeInvalidInput, "请求体无效"))
		return
	}
	display := strings.TrimSpace(req.DisplayName)
	if display == "" {
		httpx.WriteError(w, rid, errs.New(errs.CodeInvalidInput, "显示名不能为空"))
		return
	}
	u, err := h.svc.UpdateProfile(r.Context(), id, display)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, toAdminUserDTO(u))
}

func (h *Handler) deleteUser(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())
	actor, err := h.requireAdmin(r)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	id, err := userIDParam(r)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	if err := h.svc.DeleteUser(r.Context(), actor, id); err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

type setRoleReq struct {
	IsAdmin bool `json:"is_admin"`
}

func (h *Handler) setRole(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())
	actor, err := h.requireAdmin(r)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	id, err := userIDParam(r)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	var req setRoleReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		httpx.WriteError(w, rid, errs.New(errs.CodeInvalidInput, "请求体无效"))
		return
	}
	u, err := h.svc.SetAdmin(r.Context(), actor, id, req.IsAdmin)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, toAdminUserDTO(u))
}

type setEnabledReq struct {
	Enabled bool `json:"enabled"`
}

func (h *Handler) setEnabled(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())
	actor, err := h.requireAdmin(r)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	id, err := userIDParam(r)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	var req setEnabledReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		httpx.WriteError(w, rid, errs.New(errs.CodeInvalidInput, "请求体无效"))
		return
	}
	u, err := h.svc.SetEnabled(r.Context(), actor, id, req.Enabled)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, toAdminUserDTO(u))
}

type resetPasswordResp struct {
	TempPassword string `json:"temp_password"`
}

func (h *Handler) resetPassword(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())
	if _, err := h.requireAdmin(r); err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	id, err := userIDParam(r)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	temp, err := h.svc.AdminResetPassword(r.Context(), id)
	if err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, resetPasswordResp{TempPassword: temp})
}

// ===== 自助改密 =====

type changePasswordReq struct {
	OldPassword string `json:"old_password"`
	NewPassword string `json:"new_password"`
}

func (h *Handler) changePassword(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFromContext(r.Context())
	uid, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		httpx.WriteError(w, rid, errs.New(errs.CodeInternal, "missing auth context"))
		return
	}
	var req changePasswordReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		httpx.WriteError(w, rid, errs.New(errs.CodeInvalidInput, "请求体无效"))
		return
	}
	if err := h.svc.ChangePassword(r.Context(), uid, req.OldPassword, req.NewPassword); err != nil {
		httpx.WriteError(w, rid, err)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}
