package http

import (
	"encoding/json"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
	"log/slog"
	"net/http"
	"strconv"
	"strings"

	userv1 "hyapp.local/api/proto/user/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/logx"
	"hyapp/services/gateway-service/internal/auth"
)

const managerResourceGrantCapability = "manager_resource_grant"

type managerGrantResourceData struct {
	ResourceID        string `json:"resource_id"`
	ResourceType      string `json:"resource_type"`
	Name              string `json:"name"`
	AssetURL          string `json:"asset_url"`
	PreviewURL        string `json:"preview_url"`
	WalletAssetType   string `json:"wallet_asset_type"`
	WalletAssetAmount int64  `json:"wallet_asset_amount"`
	SortOrder         int32  `json:"sort_order"`
}

type businessUserLookupData struct {
	UserID        string `json:"user_id"`
	DisplayUserID string `json:"display_user_id"`
	Username      string `json:"username"`
	Avatar        string `json:"avatar"`
	Status        string `json:"status"`
	RegionID      int64  `json:"region_id,omitempty"`
	RegionCode    string `json:"region_code,omitempty"`
	RegionName    string `json:"region_name,omitempty"`
}

type managerResourceGrantData struct {
	GrantID        string `json:"grant_id"`
	CommandID      string `json:"command_id"`
	TargetUserID   string `json:"target_user_id"`
	ResourceID     string `json:"resource_id"`
	Status         string `json:"status"`
	GrantSource    string `json:"grant_source"`
	OperatorUserID string `json:"operator_user_id"`
	CreatedAtMS    int64  `json:"created_at_ms"`
}

func (h *Handler) listManagerGrantResources(writer http.ResponseWriter, request *http.Request) {
	if h.walletClient == nil || h.userHostClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	if !h.requireManagerResourceGrantCapability(writer, request) {
		return
	}
	page, pageSize, ok := managerPage(request)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	resp, err := h.walletClient.ListResources(request.Context(), &walletv1.ListResourcesRequest{
		RequestId:        httpkit.RequestIDFromContext(request.Context()),
		AppCode:          appcode.FromContext(request.Context()),
		ResourceType:     strings.TrimSpace(request.URL.Query().Get("resource_type")),
		Page:             page,
		PageSize:         pageSize,
		ActiveOnly:       true,
		ManagerGrantOnly: true,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	items := make([]managerGrantResourceData, 0, len(resp.GetResources()))
	for _, resource := range resp.GetResources() {
		items = append(items, managerGrantResourceFromProto(resource))
	}
	httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": resp.GetTotal(), "page": page, "page_size": pageSize})
}

func (h *Handler) lookupBusinessUsers(writer http.ResponseWriter, request *http.Request) {
	if h.userProfileClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	pageSize, ok := parsePositiveInt32Query(request, "page_size", 20)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	if pageSize > 20 {
		pageSize = 20
	}
	resp, err := h.userProfileClient.BusinessUserLookup(request.Context(), &userv1.BusinessUserLookupRequest{
		Meta:        authRequestMeta(request, ""),
		ActorUserId: auth.UserIDFromContext(request.Context()),
		Scene:       strings.TrimSpace(request.URL.Query().Get("scene")),
		Keyword:     strings.TrimSpace(request.URL.Query().Get("keyword")),
		PageSize:    pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	items := make([]businessUserLookupData, 0, len(resp.GetUsers()))
	for _, user := range resp.GetUsers() {
		items = append(items, businessUserLookupFromProto(user))
	}
	httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items), "page_size": pageSize})
}

func (h *Handler) grantManagerResource(writer http.ResponseWriter, request *http.Request) {
	if h.walletClient == nil || h.userHostClient == nil || h.userProfileClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	var body struct {
		CommandID       string          `json:"command_id"`
		CommandIDAlt    string          `json:"commandId"`
		TargetUserID    json.RawMessage `json:"target_user_id"`
		TargetUserIDAlt json.RawMessage `json:"targetUserId"`
		ResourceID      json.RawMessage `json:"resource_id"`
		ResourceIDAlt   json.RawMessage `json:"resourceId"`
		Reason          string          `json:"reason"`
		Quantity        *int64          `json:"quantity"`
		DurationMS      *int64          `json:"duration_ms"`
		DurationMSAlt   *int64          `json:"durationMs"`
	}
	if !decode(writer, request, &body) {
		return
	}
	if body.Quantity != nil || body.DurationMS != nil || body.DurationMSAlt != nil {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	if !h.requireManagerResourceGrantCapability(writer, request) {
		return
	}
	commandID := strings.TrimSpace(body.CommandID)
	if commandID == "" {
		commandID = strings.TrimSpace(body.CommandIDAlt)
	}
	targetUserID, targetOK := rawInt64(body.TargetUserID, body.TargetUserIDAlt)
	resourceID, resourceOK := rawInt64(body.ResourceID, body.ResourceIDAlt)
	if commandID == "" || !targetOK || targetUserID <= 0 || !resourceOK || resourceID <= 0 {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	targetResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
		Meta:   authRequestMeta(request, ""),
		UserId: targetUserID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	if target := targetResp.GetUser(); target == nil || target.GetStatus() != userv1.UserStatus_USER_STATUS_ACTIVE {
		httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
		return
	}
	reason := strings.TrimSpace(body.Reason)
	if reason == "" {
		reason = "manager_center"
	}
	actorUserID := auth.UserIDFromContext(request.Context())
	resp, err := h.walletClient.GrantResource(request.Context(), &walletv1.GrantResourceRequest{
		CommandId:      commandID,
		AppCode:        appcode.FromContext(request.Context()),
		TargetUserId:   targetUserID,
		ResourceId:     resourceID,
		Quantity:       1,
		DurationMs:     0,
		Reason:         reason,
		OperatorUserId: actorUserID,
		GrantSource:    "manager_center",
	})
	if err != nil {
		logx.Warn(request.Context(), "manager_resource_grant_failed",
			slog.Int64("actor_user_id", actorUserID),
			slog.Int64("target_user_id", targetUserID),
			slog.Int64("resource_id", resourceID),
			slog.String("command_id", commandID),
		)
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	grant := managerResourceGrantFromProto(resp.GetGrant())
	logx.Info(request.Context(), "manager_resource_grant_succeeded",
		slog.Int64("actor_user_id", actorUserID),
		slog.Int64("target_user_id", targetUserID),
		slog.Int64("resource_id", resourceID),
		slog.String("grant_id", grant.GrantID),
	)
	httpkit.WriteOK(writer, request, grant)
}

func (h *Handler) requireManagerResourceGrantCapability(writer http.ResponseWriter, request *http.Request) bool {
	resp, err := h.userHostClient.CheckBusinessCapability(request.Context(), &userv1.CheckBusinessCapabilityRequest{
		Meta:        authRequestMeta(request, ""),
		ActorUserId: auth.UserIDFromContext(request.Context()),
		Capability:  managerResourceGrantCapability,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return false
	}
	if !resp.GetAllowed() {
		httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied")
		return false
	}
	return true
}

func managerGrantResourceFromProto(resource *walletv1.Resource) managerGrantResourceData {
	if resource == nil {
		return managerGrantResourceData{}
	}
	return managerGrantResourceData{
		ResourceID:        userIDString(resource.GetResourceId()),
		ResourceType:      resource.GetResourceType(),
		Name:              resource.GetName(),
		AssetURL:          resource.GetAssetUrl(),
		PreviewURL:        resource.GetPreviewUrl(),
		WalletAssetType:   resource.GetWalletAssetType(),
		WalletAssetAmount: resource.GetWalletAssetAmount(),
		SortOrder:         resource.GetSortOrder(),
	}
}

func businessUserLookupFromProto(user *userv1.BusinessUserLookupItem) businessUserLookupData {
	if user == nil {
		return businessUserLookupData{}
	}
	return businessUserLookupData{
		UserID:        userIDString(user.GetUserId()),
		DisplayUserID: user.GetDisplayUserId(),
		Username:      user.GetUsername(),
		Avatar:        user.GetAvatar(),
		Status:        userStatusString(user.GetStatus()),
		RegionID:      user.GetRegionId(),
		RegionCode:    user.GetRegionCode(),
		RegionName:    user.GetRegionName(),
	}
}

func managerResourceGrantFromProto(grant *walletv1.ResourceGrant) managerResourceGrantData {
	if grant == nil {
		return managerResourceGrantData{}
	}
	resourceID := grant.GetGrantSubjectId()
	if len(grant.GetItems()) > 0 && grant.GetItems()[0].GetResourceId() > 0 {
		resourceID = userIDString(grant.GetItems()[0].GetResourceId())
	}
	return managerResourceGrantData{
		GrantID:        grant.GetGrantId(),
		CommandID:      grant.GetCommandId(),
		TargetUserID:   userIDString(grant.GetTargetUserId()),
		ResourceID:     resourceID,
		Status:         grant.GetStatus(),
		GrantSource:    grant.GetGrantSource(),
		OperatorUserID: userIDString(grant.GetOperatorUserId()),
		CreatedAtMS:    grant.GetCreatedAtMs(),
	}
}

func managerPage(request *http.Request) (int32, int32, bool) {
	page, pageSize, ok := resourcePage(request)
	if !ok {
		return 0, 0, false
	}
	if pageSize > 20 {
		pageSize = 20
	}
	return page, pageSize, true
}

func rawInt64(values ...json.RawMessage) (int64, bool) {
	for _, raw := range values {
		value, ok := parseRawInt64(raw)
		if ok {
			return value, true
		}
	}
	return 0, false
}

func parseRawInt64(raw json.RawMessage) (int64, bool) {
	text := strings.TrimSpace(string(raw))
	if text == "" || text == "null" {
		return 0, false
	}
	if strings.HasPrefix(text, `"`) {
		var value string
		if err := json.Unmarshal(raw, &value); err != nil {
			return 0, false
		}
		text = strings.TrimSpace(value)
	}
	value, err := strconv.ParseInt(text, 10, 64)
	return value, err == nil
}

func userStatusString(status userv1.UserStatus) string {
	switch status {
	case userv1.UserStatus_USER_STATUS_ACTIVE:
		return "active"
	case userv1.UserStatus_USER_STATUS_DISABLED:
		return "disabled"
	case userv1.UserStatus_USER_STATUS_BANNED:
		return "banned"
	default:
		return "unspecified"
	}
}
