package resource

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/model"
	"hyapp-admin-server/internal/response"
	walletv1 "hyapp.local/api/proto/wallet/v1"

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

const resourceIdentityAutoGrantConfigGroup = "resource-identity-auto-grant-config"

var resourceIdentityAutoGrantTypes = []string{"host", "bd", "bd_leader", "agency", "manager"}

type resourceIdentityAutoGrantConfigDTO struct {
	Items            []resourceIdentityAutoGrantItemDTO `json:"items"`
	UpdatedByAdminID int64                              `json:"updatedByAdminId"`
	CreatedAtMS      int64                              `json:"createdAtMs"`
	UpdatedAtMS      int64                              `json:"updatedAtMs"`
}

type resourceIdentityAutoGrantItemDTO struct {
	IdentityType    string            `json:"identityType"`
	ResourceGroupID int64             `json:"resourceGroupId"`
	ResourceGroup   *resourceGroupDTO `json:"resourceGroup,omitempty"`
}

type resourceIdentityAutoGrantConfigValue struct {
	Items            []resourceIdentityAutoGrantStoredItem `json:"items"`
	UpdatedByAdminID int64                                 `json:"updated_by_admin_id"`
}

type resourceIdentityAutoGrantStoredItem struct {
	IdentityType    string `json:"identity_type"`
	ResourceGroupID int64  `json:"resource_group_id"`
}

type resourceIdentityAutoGrantConfigRequest struct {
	Items []resourceIdentityAutoGrantItemRequest `json:"items"`
}

type resourceIdentityAutoGrantItemRequest struct {
	IdentityType      string `json:"identity_type"`
	IdentityTypeCamel string `json:"identityType"`
	ResourceGroupID   int64  `json:"resource_group_id"`
	ResourceGroupIDJS int64  `json:"resourceGroupId"`
}

func (h *Handler) GetResourceIdentityAutoGrantConfig(c *gin.Context) {
	if h.store == nil {
		response.ServerError(c, "后台配置存储未初始化")
		return
	}

	// 身份资源组属于 admin 后台低频配置，不进入 wallet 协议；读取时从 admin_app_configs 取当前 App 的快照。
	config, err := h.loadIdentityAutoGrantConfig(c)
	if err != nil {
		response.ServerError(c, "加载身份资源组配置失败")
		return
	}
	response.OK(c, config)
}

func (h *Handler) UpdateResourceIdentityAutoGrantConfig(c *gin.Context) {
	if h.store == nil {
		response.ServerError(c, "后台配置存储未初始化")
		return
	}
	var req resourceIdentityAutoGrantConfigRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "身份资源组配置参数不正确")
		return
	}

	// 前端保存的是整份配置；后端也要求 5 个身份一次性提交，避免半包请求把未提交身份误清空。
	items, err := normalizeIdentityAutoGrantItems(req.Items)
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	// resource_group_id=0 表示不自动发放；大于 0 必须先确认 wallet 中存在，防止保存悬空资源组。
	if err := h.validateIdentityAutoGrantResourceGroups(c, items); err != nil {
		response.BadRequest(c, err.Error())
		return
	}

	payload, err := json.Marshal(resourceIdentityAutoGrantConfigValue{
		Items:            items,
		UpdatedByAdminID: actorID(c),
	})
	if err != nil {
		response.ServerError(c, "保存身份资源组配置失败")
		return
	}
	appCode := appctx.FromContext(c.Request.Context())
	// 配置按 App 隔离；Key 使用中间件解析出的 app_code，禁止请求体自行覆盖作用域。
	item := model.AppConfig{
		Group:       resourceIdentityAutoGrantConfigGroup,
		Key:         appCode,
		Value:       string(payload),
		Description: "用户身份自动发放资源组配置",
	}
	if err := h.store.UpsertAppConfigs([]model.AppConfig{item}); err != nil {
		response.ServerError(c, "保存身份资源组配置失败")
		return
	}

	// 保存后重新读取并补资源组详情，保证返回值和 GET 接口结构一致，前端无需自己拼保存后的展示数据。
	config, err := h.loadIdentityAutoGrantConfig(c)
	if err != nil {
		response.ServerError(c, "加载身份资源组配置失败")
		return
	}
	h.auditLog(c, "update-identity-auto-grant-config", "resource_identity_auto_grant_configs", appCode, "success", identityAutoGrantAuditDetail(items))
	response.OK(c, config)
}

func (h *Handler) loadIdentityAutoGrantConfig(c *gin.Context) (resourceIdentityAutoGrantConfigDTO, error) {
	appCode := appctx.FromContext(c.Request.Context())
	item, err := h.store.GetAppConfig(resourceIdentityAutoGrantConfigGroup, appCode)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		// 首次进入页面没有配置行时返回完整身份集合，前端可以直接展示空配置而不是走错误态。
		return resourceIdentityAutoGrantConfigDTO{Items: defaultIdentityAutoGrantItems()}, nil
	}
	if err != nil {
		return resourceIdentityAutoGrantConfigDTO{}, err
	}
	config, err := identityAutoGrantConfigFromValue(item.Value)
	if err != nil {
		return resourceIdentityAutoGrantConfigDTO{}, err
	}
	config.CreatedAtMS = item.CreatedAtMS
	config.UpdatedAtMS = item.UpdatedAtMS
	// 资源组详情只用于展示已选项名称；补全失败不应该让整个配置页打不开。
	h.fillIdentityAutoGrantResourceGroups(c, config.Items)
	return config, nil
}

func identityAutoGrantConfigFromValue(raw string) (resourceIdentityAutoGrantConfigDTO, error) {
	var value resourceIdentityAutoGrantConfigValue
	if strings.TrimSpace(raw) != "" {
		if err := json.Unmarshal([]byte(raw), &value); err != nil {
			return resourceIdentityAutoGrantConfigDTO{}, err
		}
	}
	itemsByType := make(map[string]resourceIdentityAutoGrantStoredItem, len(value.Items))
	for _, item := range value.Items {
		identityType := strings.TrimSpace(item.IdentityType)
		if isIdentityAutoGrantType(identityType) && item.ResourceGroupID >= 0 {
			// 读取历史配置时按当前支持的身份集合过滤，旧脏数据不继续扩散到前端表单。
			itemsByType[identityType] = resourceIdentityAutoGrantStoredItem{IdentityType: identityType, ResourceGroupID: item.ResourceGroupID}
		}
	}
	items := make([]resourceIdentityAutoGrantItemDTO, 0, len(resourceIdentityAutoGrantTypes))
	for _, identityType := range resourceIdentityAutoGrantTypes {
		// 输出顺序固定，避免前端每次打开弹窗时身份行抖动。
		item := itemsByType[identityType]
		items = append(items, resourceIdentityAutoGrantItemDTO{
			IdentityType:    identityType,
			ResourceGroupID: item.ResourceGroupID,
		})
	}
	return resourceIdentityAutoGrantConfigDTO{Items: items, UpdatedByAdminID: value.UpdatedByAdminID}, nil
}

func defaultIdentityAutoGrantItems() []resourceIdentityAutoGrantItemDTO {
	items := make([]resourceIdentityAutoGrantItemDTO, 0, len(resourceIdentityAutoGrantTypes))
	for _, identityType := range resourceIdentityAutoGrantTypes {
		items = append(items, resourceIdentityAutoGrantItemDTO{IdentityType: identityType})
	}
	return items
}

func normalizeIdentityAutoGrantItems(items []resourceIdentityAutoGrantItemRequest) ([]resourceIdentityAutoGrantStoredItem, error) {
	if len(items) != len(resourceIdentityAutoGrantTypes) {
		return nil, fmt.Errorf("必须配置所有身份")
	}

	itemByType := make(map[string]resourceIdentityAutoGrantStoredItem, len(items))
	for _, item := range items {
		// 当前前端提交 snake_case；这里兼容 camelCase，避免旧构建或调试脚本因为字段名差异无法保存。
		identityType := strings.TrimSpace(firstNonEmpty(item.IdentityType, item.IdentityTypeCamel))
		if !isIdentityAutoGrantType(identityType) {
			return nil, fmt.Errorf("身份参数不正确")
		}
		if _, exists := itemByType[identityType]; exists {
			return nil, fmt.Errorf("身份不能重复配置")
		}
		// 0 是显式关闭自动发放，所以只拒绝负数；正数存在性在后续 wallet 查询里验证。
		resourceGroupID := item.ResourceGroupID
		if resourceGroupID == 0 {
			resourceGroupID = item.ResourceGroupIDJS
		}
		if resourceGroupID < 0 {
			return nil, fmt.Errorf("资源组必须是有效资源组")
		}
		itemByType[identityType] = resourceIdentityAutoGrantStoredItem{
			IdentityType:    identityType,
			ResourceGroupID: resourceGroupID,
		}
	}

	normalized := make([]resourceIdentityAutoGrantStoredItem, 0, len(resourceIdentityAutoGrantTypes))
	for _, identityType := range resourceIdentityAutoGrantTypes {
		// 按白名单顺序重建数组，存储层不依赖前端传入顺序。
		item, ok := itemByType[identityType]
		if !ok {
			return nil, fmt.Errorf("必须配置所有身份")
		}
		normalized = append(normalized, item)
	}
	return normalized, nil
}

func (h *Handler) validateIdentityAutoGrantResourceGroups(c *gin.Context, items []resourceIdentityAutoGrantStoredItem) error {
	if h.wallet == nil {
		for _, item := range items {
			if item.ResourceGroupID > 0 {
				// 没有 wallet client 时只能接受全关闭配置，避免把未校验的正数资源组写入运行配置。
				return fmt.Errorf("资源组服务未初始化")
			}
		}
		return nil
	}

	checked := make(map[int64]struct{}, len(items))
	ctx, cancel := h.walletRequestContext(c)
	defer cancel()
	for _, item := range items {
		if item.ResourceGroupID == 0 {
			continue
		}
		if _, ok := checked[item.ResourceGroupID]; ok {
			continue
		}
		checked[item.ResourceGroupID] = struct{}{}
		// 保存前只校验唯一资源组 ID，一份配置里多个身份共用同组时不重复打 wallet。
		_, err := h.wallet.GetResourceGroup(ctx, &walletv1.GetResourceGroupRequest{
			RequestId: middleware.CurrentRequestID(c),
			AppCode:   appctx.FromContext(c.Request.Context()),
			GroupId:   item.ResourceGroupID,
		})
		if err != nil {
			return fmt.Errorf("资源组 %d 不存在或不可用", item.ResourceGroupID)
		}
	}
	return nil
}

func (h *Handler) fillIdentityAutoGrantResourceGroups(c *gin.Context, items []resourceIdentityAutoGrantItemDTO) {
	if h.wallet == nil {
		return
	}
	groupByID := make(map[int64]*resourceGroupDTO, len(items))
	ctx, cancel := h.walletRequestContext(c)
	defer cancel()
	for index := range items {
		groupID := items[index].ResourceGroupID
		if groupID == 0 {
			continue
		}
		if group, ok := groupByID[groupID]; ok {
			items[index].ResourceGroup = group
			continue
		}
		// 已保存配置可能指向被禁用或被删除的资源组；详情查询失败时保留 ID，让前端至少能展示 #ID 并允许重新选择。
		resp, err := h.wallet.GetResourceGroup(ctx, &walletv1.GetResourceGroupRequest{
			RequestId: middleware.CurrentRequestID(c),
			AppCode:   appctx.FromContext(c.Request.Context()),
			GroupId:   groupID,
		})
		if err != nil {
			continue
		}
		group := resourceGroupFromProto(resp.GetGroup())
		groupByID[groupID] = &group
		items[index].ResourceGroup = &group
	}
}

func isIdentityAutoGrantType(value string) bool {
	for _, candidate := range resourceIdentityAutoGrantTypes {
		if value == candidate {
			return true
		}
	}
	return false
}

func identityAutoGrantAuditDetail(items []resourceIdentityAutoGrantStoredItem) string {
	parts := make([]string, 0, len(items))
	for _, item := range items {
		parts = append(parts, fmt.Sprintf("%s=%d", item.IdentityType, item.ResourceGroupID))
	}
	return strings.Join(parts, ",")
}
