// Package notify 内的 prefs_handler.go 实现 per-user 通知偏好的 HTTP 接口：
//
//	GET /api/v1/notifications/prefs  → 当前用户的偏好（im_webhook_url 不回显明文，
//	                                   仅返回 im_webhook_url_set 布尔标记是否已配置）。
//	PUT /api/v1/notifications/prefs  → 整体更新偏好。im_webhook_url 提供非空字符串
//	                                   则加密落库；提供空字符串则清除；字段省略则保留原值。
//
// im_webhook_url 经 Encryptor 加密为 im_webhook_url_enc BYTEA。偏好只在 Handler
// 注入了 prefsRepo + enc 后才挂载（WithPrefs），未配置 crypto 时该路由不暴露。
package notify

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

	"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"
)

// prefsDTO 是偏好的对外 JSON 形态。im_webhook_url 绝不回显明文：读取时只暴露
// im_webhook_url_set 标记是否已配置，写入时通过单独的可空字段提交。
type prefsDTO struct {
	EmailEnabled    bool   `json:"email_enabled"`
	IMEnabled       bool   `json:"im_enabled"`
	IMWebhookURLSet bool   `json:"im_webhook_url_set"`
	MinSeverity     string `json:"min_severity"`
}

// prefsUpdateReq 是 PUT 请求体。IMWebhookURL 用指针区分三态：
//   - nil（字段省略）  → 保留原密文不变；
//   - 指向空字符串 ""  → 清除已配置的 webhook；
//   - 指向非空字符串    → 加密为新密文。
type prefsUpdateReq struct {
	EmailEnabled *bool   `json:"email_enabled"`
	IMEnabled    *bool   `json:"im_enabled"`
	IMWebhookURL *string `json:"im_webhook_url"`
	MinSeverity  *string `json:"min_severity"`
}

// getPrefs 处理 GET /api/v1/notifications/prefs。
func (h *Handler) getPrefs(w http.ResponseWriter, r *http.Request) {
	uid, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()),
			errs.New(errs.CodeUnauthorized, "未认证"))
		return
	}
	p, err := h.prefs.Get(r.Context(), uid)
	if err != nil {
		httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()), err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, prefsDTO{
		EmailEnabled:    p.EmailEnabled,
		IMEnabled:       p.IMEnabled,
		IMWebhookURLSet: len(p.ImWebhookURLEnc) > 0,
		MinSeverity:     string(p.MinSeverity),
	})
}

// putPrefs 处理 PUT /api/v1/notifications/prefs。
func (h *Handler) putPrefs(w http.ResponseWriter, r *http.Request) {
	uid, ok := middleware.UserIDFromContext(r.Context())
	if !ok {
		httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()),
			errs.New(errs.CodeUnauthorized, "未认证"))
		return
	}
	var req prefsUpdateReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()),
			errs.New(errs.CodeInvalidInput, "请求体格式错误"))
		return
	}

	cur, err := h.prefs.Get(r.Context(), uid)
	if err != nil {
		httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()), err)
		return
	}
	if req.EmailEnabled != nil {
		cur.EmailEnabled = *req.EmailEnabled
	}
	if req.IMEnabled != nil {
		cur.IMEnabled = *req.IMEnabled
	}
	if req.MinSeverity != nil {
		sev := Severity(strings.TrimSpace(*req.MinSeverity))
		if !validSeverity(sev) {
			httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()),
				errs.New(errs.CodeInvalidInput, "min_severity 取值非法（info|warning|error）"))
			return
		}
		cur.MinSeverity = sev
	}
	if req.IMWebhookURL != nil {
		url := strings.TrimSpace(*req.IMWebhookURL)
		if url == "" {
			cur.ImWebhookURLEnc = nil
		} else {
			ct, err := h.enc.Encrypt([]byte(url))
			if err != nil {
				httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()),
					errs.Wrap(err, errs.CodeInternal, "加密 im_webhook_url"))
				return
			}
			cur.ImWebhookURLEnc = ct
		}
	}

	if err := h.prefs.Upsert(r.Context(), cur); err != nil {
		httpx.WriteError(w, middleware.RequestIDFromContext(r.Context()), err)
		return
	}
	httpx.WriteJSON(w, http.StatusOK, prefsDTO{
		EmailEnabled:    cur.EmailEnabled,
		IMEnabled:       cur.IMEnabled,
		IMWebhookURLSet: len(cur.ImWebhookURLEnc) > 0,
		MinSeverity:     string(cur.MinSeverity),
	})
}

// validSeverity 报告 s 是否为合法的 min_severity 取值。
func validSeverity(s Severity) bool {
	switch s {
	case SeverityInfo, SeverityWarning, SeverityError:
		return true
	default:
		return false
	}
}
