package fullservernotice

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/activityclient"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/response"
	activityv1 "hyapp.local/api/proto/activity/v1"

	"github.com/gin-gonic/gin"
)

const (
	messageTypeSystem   = "system"
	messageTypeActivity = "activity"

	targetScopeSingleUser     = "single_user"
	targetScopeUserIDs        = "user_ids"
	targetScopeRegion         = "region"
	targetScopeCountry        = "country"
	targetScopeAllActiveUsers = "all_active_users"
	targetScopeAllRegistered  = "all_registered_users"

	defaultProducerEventType = "admin_full_server_notice"
	defaultBatchSize         = 500
)

type Handler struct {
	activity       activityclient.Client
	requestTimeout time.Duration
	audit          shared.OperationLogger
}

func New(activity activityclient.Client, audit shared.OperationLogger) *Handler {
	return &Handler{activity: activity, requestTimeout: 3 * time.Second, audit: audit}
}

type fanoutRequest struct {
	CommandID         string               `json:"command_id"`
	MessageType       string               `json:"message_type"`
	TargetScope       string               `json:"target_scope"`
	TargetUserID      int64                `json:"target_user_id"`
	UserIDs           int64List            `json:"user_ids"`
	RegionID          int64                `json:"region_id"`
	Country           string               `json:"country"`
	ProducerEventType string               `json:"producer_event_type"`
	AggregateType     string               `json:"aggregate_type"`
	AggregateID       string               `json:"aggregate_id"`
	TemplateID        string               `json:"template_id"`
	TemplateVersion   string               `json:"template_version"`
	Title             string               `json:"title"`
	Summary           string               `json:"summary"`
	Body              string               `json:"body"`
	IconURL           string               `json:"icon_url"`
	ImageURL          string               `json:"image_url"`
	Actions           []inboxActionRequest `json:"actions"`
	ActionType        string               `json:"action_type"`
	ActionParam       string               `json:"action_param"`
	Priority          int32                `json:"priority"`
	SentAtMS          int64                `json:"sent_at_ms"`
	ExpireAtMS        int64                `json:"expire_at_ms"`
	MetadataJSON      string               `json:"metadata_json"`
	BatchSize         int32                `json:"batch_size"`
}

type inboxActionRequest struct {
	Action string                `json:"action"`
	Label  string                `json:"label"`
	Style  string                `json:"style"`
	API    inboxActionAPIRequest `json:"api"`
}

type inboxActionAPIRequest struct {
	Method string         `json:"method"`
	Path   string         `json:"path"`
	Query  map[string]any `json:"query,omitempty"`
	Body   any            `json:"body,omitempty"`
}

type fanoutDTO struct {
	JobID       string `json:"job_id"`
	Status      string `json:"status"`
	Created     bool   `json:"created"`
	CommandID   string `json:"command_id"`
	MessageType string `json:"message_type"`
	TargetScope string `json:"target_scope"`
}

func (h *Handler) CreateFanout(c *gin.Context) {
	var req fanoutRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "全服通知参数不正确")
		return
	}
	normalized, err := normalizeFanoutRequest(req, int64(middleware.CurrentUserID(c)))
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	targetFilterJSON, err := buildTargetFilterJSON(normalized)
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	templateSnapshotJSON, err := buildTemplateSnapshotJSON(normalized)
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}

	ctx, cancel := h.activityContext(c)
	defer cancel()
	// 全服通知只向 activity-service 创建 fanout 命令；真正逐用户写入由 message fanout worker 执行。
	// command_id 作为后台操作幂等键，重复提交同一 payload 会返回同一个 job，不会重复刷用户收件箱。
	resp, err := h.activity.CreateFanoutJob(ctx, &activityv1.CreateFanoutJobRequest{
		Meta:                 h.meta(c),
		CommandId:            normalized.CommandID,
		MessageType:          normalized.MessageType,
		TargetScope:          normalized.TargetScope,
		TargetFilterJson:     targetFilterJSON,
		TemplateSnapshotJson: templateSnapshotJSON,
		BatchSize:            normalized.BatchSize,
		CreatedBy:            "admin:" + strconv.FormatInt(int64(middleware.CurrentUserID(c)), 10),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	item := fanoutDTO{
		JobID:       resp.GetJobId(),
		Status:      resp.GetStatus(),
		Created:     resp.GetCreated(),
		CommandID:   normalized.CommandID,
		MessageType: normalized.MessageType,
		TargetScope: normalized.TargetScope,
	}
	shared.OperationLogWithResourceID(c, h.audit, "create-full-server-notice-fanout", "message_fanout_jobs", item.JobID, "success", normalized.Title)
	response.Created(c, item)
}

func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
	return &activityv1.RequestMeta{
		RequestId: middleware.CurrentRequestID(c),
		Caller:    "admin-server",
		AppCode:   appctx.FromContext(c.Request.Context()),
		SentAtMs:  time.Now().UTC().UnixMilli(),
	}
}

func (h *Handler) activityContext(c *gin.Context) (context.Context, context.CancelFunc) {
	timeout := h.requestTimeout
	if timeout <= 0 {
		timeout = 3 * time.Second
	}
	return context.WithTimeout(c.Request.Context(), timeout)
}

func normalizeFanoutRequest(req fanoutRequest, adminID int64) (fanoutRequest, error) {
	req.MessageType = strings.ToLower(strings.TrimSpace(req.MessageType))
	if req.MessageType == "" {
		req.MessageType = messageTypeSystem
	}
	if req.MessageType != messageTypeSystem && req.MessageType != messageTypeActivity {
		return fanoutRequest{}, fmt.Errorf("message_type 只支持 system 或 activity")
	}
	req.TargetScope = strings.TrimSpace(req.TargetScope)
	if req.TargetScope == "" {
		req.TargetScope = targetScopeAllActiveUsers
	}
	if !allowedTargetScope(req.TargetScope) {
		return fanoutRequest{}, fmt.Errorf("target_scope 不正确")
	}
	req.Title = strings.TrimSpace(req.Title)
	req.Summary = strings.TrimSpace(req.Summary)
	req.Body = strings.TrimSpace(req.Body)
	req.IconURL = strings.TrimSpace(req.IconURL)
	req.ImageURL = strings.TrimSpace(req.ImageURL)
	for index := range req.Actions {
		req.Actions[index].Action = strings.ToLower(strings.TrimSpace(req.Actions[index].Action))
		req.Actions[index].Label = strings.TrimSpace(req.Actions[index].Label)
		req.Actions[index].Style = strings.ToLower(strings.TrimSpace(req.Actions[index].Style))
		req.Actions[index].API.Method = strings.ToUpper(strings.TrimSpace(req.Actions[index].API.Method))
		req.Actions[index].API.Path = strings.TrimSpace(req.Actions[index].API.Path)
	}
	req.ActionType = strings.TrimSpace(req.ActionType)
	req.ActionParam = strings.TrimSpace(req.ActionParam)
	req.ProducerEventType = firstText(req.ProducerEventType, defaultProducerEventType)
	req.AggregateType = firstText(req.AggregateType, "admin_notice")
	req.AggregateID = strings.TrimSpace(req.AggregateID)
	req.TemplateID = strings.TrimSpace(req.TemplateID)
	req.TemplateVersion = strings.TrimSpace(req.TemplateVersion)
	req.Country = strings.TrimSpace(req.Country)
	req.MetadataJSON = strings.TrimSpace(req.MetadataJSON)
	if req.Title == "" || req.Summary == "" {
		return fanoutRequest{}, fmt.Errorf("title 和 summary 不能为空")
	}
	if req.ExpireAtMS > 0 && req.SentAtMS > 0 && req.ExpireAtMS <= req.SentAtMS {
		return fanoutRequest{}, fmt.Errorf("expire_at_ms 必须晚于 sent_at_ms")
	}
	if req.MetadataJSON != "" && !json.Valid([]byte(req.MetadataJSON)) {
		return fanoutRequest{}, fmt.Errorf("metadata_json 不是合法 JSON")
	}
	if req.BatchSize <= 0 {
		req.BatchSize = defaultBatchSize
	}
	if req.CommandID == "" {
		req.CommandID = fmt.Sprintf("admin_notice_%d_%d", adminID, time.Now().UTC().UnixMilli())
	}
	req.CommandID = strings.TrimSpace(req.CommandID)
	if req.AggregateID == "" {
		req.AggregateID = req.CommandID
	}
	return req, nil
}

func buildTargetFilterJSON(req fanoutRequest) (string, error) {
	var payload map[string]any
	switch req.TargetScope {
	case targetScopeAllActiveUsers, targetScopeAllRegistered:
		payload = map[string]any{}
	case targetScopeSingleUser:
		if req.TargetUserID <= 0 {
			return "", fmt.Errorf("target_user_id 不能为空")
		}
		payload = map[string]any{"target_user_id": req.TargetUserID}
	case targetScopeUserIDs:
		userIDs := positiveUniqueInt64s([]int64(req.UserIDs))
		if len(userIDs) == 0 {
			return "", fmt.Errorf("user_ids 不能为空")
		}
		payload = map[string]any{"user_ids": userIDs}
	case targetScopeRegion:
		if req.RegionID <= 0 {
			return "", fmt.Errorf("region_id 不能为空")
		}
		payload = map[string]any{"region_id": req.RegionID}
	case targetScopeCountry:
		if req.Country == "" {
			return "", fmt.Errorf("country 不能为空")
		}
		payload = map[string]any{"country": req.Country}
	default:
		return "", fmt.Errorf("target_scope 不正确")
	}
	encoded, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	return string(encoded), nil
}

func buildTemplateSnapshotJSON(req fanoutRequest) (string, error) {
	payload := map[string]any{
		"producer_event_type": req.ProducerEventType,
		"aggregate_type":      req.AggregateType,
		"aggregate_id":        req.AggregateID,
		"template_id":         req.TemplateID,
		"template_version":    req.TemplateVersion,
		"title":               req.Title,
		"summary":             req.Summary,
		"body":                req.Body,
		"icon_url":            req.IconURL,
		"image_url":           req.ImageURL,
		"actions":             req.Actions,
		"action_type":         req.ActionType,
		"action_param":        req.ActionParam,
		"priority":            req.Priority,
		"sent_at_ms":          req.SentAtMS,
		"expire_at_ms":        req.ExpireAtMS,
	}
	if req.MetadataJSON != "" {
		var parsed any
		if err := json.Unmarshal([]byte(req.MetadataJSON), &parsed); err != nil {
			return "", fmt.Errorf("metadata_json 不是合法 JSON")
		}
		payload["metadata_json"] = parsed
	}
	encoded, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	return string(encoded), nil
}

func allowedTargetScope(scope string) bool {
	switch scope {
	case targetScopeSingleUser, targetScopeUserIDs, targetScopeRegion, targetScopeCountry, targetScopeAllActiveUsers, targetScopeAllRegistered:
		return true
	default:
		return false
	}
}

type int64List []int64

func (values *int64List) UnmarshalJSON(data []byte) error {
	var rawItems []json.RawMessage
	if err := json.Unmarshal(data, &rawItems); err != nil {
		return err
	}
	parsed := make([]int64, 0, len(rawItems))
	for _, rawItem := range rawItems {
		value, err := parseJSONInt64(rawItem)
		if err != nil {
			return err
		}
		parsed = append(parsed, value)
	}
	*values = parsed
	return nil
}

func parseJSONInt64(raw json.RawMessage) (int64, error) {
	text := strings.TrimSpace(string(raw))
	if strings.HasPrefix(text, `"`) {
		unquoted, err := strconv.Unquote(text)
		if err != nil {
			return 0, err
		}
		text = strings.TrimSpace(unquoted)
	}
	value, err := strconv.ParseInt(text, 10, 64)
	if err != nil {
		return 0, err
	}
	return value, nil
}

func positiveUniqueInt64s(values []int64) []int64 {
	seen := make(map[int64]struct{}, len(values))
	out := make([]int64, 0, len(values))
	for _, value := range values {
		if value <= 0 {
			continue
		}
		if _, exists := seen[value]; exists {
			continue
		}
		seen[value] = struct{}{}
		out = append(out, value)
	}
	return out
}

func firstText(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}
