// Package imgroup 定义腾讯云 IM GroupID 的唯一生成和解析规则。
// gateway、room-service、activity-service 必须共享这里的规则，避免某个服务手写字符串后把播报群误当房间群。
package imgroup

import (
	"fmt"
	"strconv"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/roomid"
)

const (
	// KindUnknown 表示 GroupID 既不是本项目播报群，也不是合法房间群；回调层必须 fail-closed。
	KindUnknown Kind = "unknown"
	// KindRoom 保持已有房间群规则：腾讯 IM GroupID 直接等于内部 room_id。
	KindRoom Kind = "room"
	// KindGlobalBroadcast 是 App 维度全局播报群，只允许服务端管理员账号发消息。
	KindGlobalBroadcast Kind = "global_broadcast"
	// KindRegionBroadcast 是区域播报群，用户只能加入自己 user-service 归属的区域群。
	KindRegionBroadcast Kind = "region_broadcast"

	// globalSuffix/regionToken 是可解析协议的一部分；不要在业务代码里重复拼接这些片段。
	// globalSuffix 是 v1 全局播报群后缀：ChatRoom 类型受套餐成员上限约束（线上已触发 10038），
	// 仅保留解析与过渡期双发能力，新群一律生成 v2。
	globalSuffix = "_bc_g"
	// globalSuffixV2 是 v2 全局播报群后缀：AVChatRoom 类型，成员数不受套餐上限约束。
	globalSuffixV2 = "_bc_g_v2"
	// regionToken 是 v1 区域播报群片段：与 v1 全局群同样受 ChatRoom 成员上限约束，
	// 仅保留解析与过渡期双发能力，新群一律生成 v2。
	regionToken = "_bc_r_"
	// regionTokenV2 是 v2 区域播报群片段：AVChatRoom 类型，成员数不受套餐上限约束。
	regionTokenV2 = "_bc_r_v2_"
)

// Kind 是回调分流和客户端 join 列表使用的稳定分类，不直接暴露腾讯云原始字符串判断。
type Kind string

// ParsedGroup 是 GroupID 的强类型解析结果；只有对应 Kind 的字段才有业务含义。
type ParsedGroup struct {
	Kind     Kind
	AppCode  string
	RegionID int64
	RoomID   string
}

// GlobalBroadcastGroupID 生成 App 全局播报群 ID（v2，AVChatRoom）。
// app_code 先 Normalize 再进入 GroupID，确保 HTTP、gRPC、outbox 和腾讯回调看到的是同一租户标识。
func GlobalBroadcastGroupID(value string) (string, error) {
	return GlobalBroadcastGroupIDWithPrefix("", value)
}

// GlobalBroadcastGroupIDWithPrefix 生成带环境前缀的 App 全局播报群 ID（v2，AVChatRoom）。
func GlobalBroadcastGroupIDWithPrefix(prefix string, value string) (string, error) {
	return globalBroadcastGroupIDWithSuffix(prefix, value, globalSuffixV2)
}

// LegacyGlobalBroadcastGroupIDWithPrefix 生成 v1 全局播报群 ID。
// 仅供过渡期双发使用：老客户端在拉取新 join 列表前仍停留在 v1 群里。
func LegacyGlobalBroadcastGroupIDWithPrefix(prefix string, value string) (string, error) {
	return globalBroadcastGroupIDWithSuffix(prefix, value, globalSuffix)
}

func globalBroadcastGroupIDWithSuffix(prefix string, value string, suffix string) (string, error) {
	prefix = strings.TrimSpace(prefix)
	if err := validateGroupIDPrefix(prefix); err != nil {
		return "", err
	}
	app := appcode.Normalize(value)
	if err := validateAppCodeForGroupID(app); err != nil {
		return "", err
	}
	groupID := prefix + "hy_" + app + suffix
	if len(groupID) > roomid.MaxStringIDLength {
		return "", fmt.Errorf("global broadcast group_id is too long")
	}
	return groupID, nil
}

// RegionBroadcastGroupID 生成区域播报群 ID（v2，AVChatRoom）。
// region_id <= 0 被拒绝，GLOBAL/未知区域不能变成一个可 join 的区域群。
func RegionBroadcastGroupID(value string, regionID int64) (string, error) {
	return RegionBroadcastGroupIDWithPrefix("", value, regionID)
}

// RegionBroadcastGroupIDWithPrefix 生成带环境前缀的区域播报群 ID（v2，AVChatRoom）。
func RegionBroadcastGroupIDWithPrefix(prefix string, value string, regionID int64) (string, error) {
	return regionBroadcastGroupIDWithToken(prefix, value, regionID, regionTokenV2)
}

// LegacyRegionBroadcastGroupIDWithPrefix 生成 v1 区域播报群 ID。
// 仅供过渡期双发和旧群成员移除使用：老客户端在拉取新 join 列表前仍停留在 v1 群里。
func LegacyRegionBroadcastGroupIDWithPrefix(prefix string, value string, regionID int64) (string, error) {
	return regionBroadcastGroupIDWithToken(prefix, value, regionID, regionToken)
}

func regionBroadcastGroupIDWithToken(prefix string, value string, regionID int64, token string) (string, error) {
	prefix = strings.TrimSpace(prefix)
	if err := validateGroupIDPrefix(prefix); err != nil {
		return "", err
	}
	app := appcode.Normalize(value)
	if err := validateAppCodeForGroupID(app); err != nil {
		return "", err
	}
	if regionID <= 0 {
		return "", fmt.Errorf("region_id must be positive")
	}
	groupID := prefix + "hy_" + app + token + strconv.FormatInt(regionID, 10)
	if len(groupID) > roomid.MaxStringIDLength {
		return "", fmt.Errorf("region broadcast group_id is too long")
	}
	return groupID, nil
}

// RoomGroupID 生成腾讯云 IM 房间群 ID。
func RoomGroupID(value string) (string, error) {
	return RoomGroupIDWithPrefix("", value)
}

// RoomGroupIDWithPrefix 生成带环境前缀的腾讯云 IM 房间群 ID。
func RoomGroupIDWithPrefix(prefix string, value string) (string, error) {
	prefix = strings.TrimSpace(prefix)
	if err := validateGroupIDPrefix(prefix); err != nil {
		return "", err
	}
	room := strings.TrimSpace(value)
	if !roomid.ValidStringID(room) {
		return "", fmt.Errorf("room_id is invalid for group_id")
	}
	// 房间群 GroupID 必须和内部 room_id 保持一致；线上已有腾讯 IM 群也是这个 ID，不能在投递边界改写。
	groupID := prefix + room
	if len(groupID) > roomid.MaxStringIDLength {
		return "", fmt.Errorf("room group_id is too long")
	}
	return groupID, nil
}

// Parse 在调用任何 room guard 前完成 GroupID 分类。
// 解析顺序必须先播报群、后房间群；否则 hy_<app>_bc_* 这类合法短字符串可能被 roomid 规则吞掉。
func Parse(groupID string) ParsedGroup {
	groupID = strings.TrimSpace(groupID)
	if groupID == "" {
		return ParsedGroup{Kind: KindUnknown}
	}

	if parsed := parseBroadcastGroupID(groupID); parsed.Kind != KindUnknown {
		return parsed
	}
	if strings.HasPrefix(groupID, "hy_") && strings.Contains(groupID, "_bc_") {
		return ParsedGroup{Kind: KindUnknown}
	}

	if roomID, ok := RoomIDFromRoomGroupID(groupID); ok {
		return ParsedGroup{Kind: KindRoom, RoomID: roomID}
	}
	return ParsedGroup{Kind: KindUnknown}
}

// ParseWithPrefix 使用配置的环境前缀解析腾讯云回调里的 GroupID。
func ParseWithPrefix(groupID string, prefix string) ParsedGroup {
	groupID = strings.TrimSpace(groupID)
	prefix = strings.TrimSpace(prefix)
	if groupID == "" || validateGroupIDPrefix(prefix) != nil {
		return ParsedGroup{Kind: KindUnknown}
	}
	if prefix == "" {
		return Parse(groupID)
	}
	if after, ok := strings.CutPrefix(groupID, prefix); ok {
		if parsed := parseBroadcastGroupID(after); parsed.Kind != KindUnknown {
			return parsed
		}
		if roomID, ok := RoomIDFromRoomGroupID(after); ok {
			return ParsedGroup{Kind: KindRoom, RoomID: roomID}
		}
		return ParsedGroup{Kind: KindUnknown}
	}
	if strings.HasPrefix(groupID, "hy_") && strings.Contains(groupID, "_bc_") {
		return ParsedGroup{Kind: KindUnknown}
	}
	if roomID, ok := RoomIDFromRoomGroupID(groupID); ok {
		return ParsedGroup{Kind: KindRoom, RoomID: roomID}
	}
	return ParsedGroup{Kind: KindUnknown}
}

// RoomIDFromRoomGroupID 把腾讯云 IM 房间群 ID 还原成内部 room_id。
func RoomIDFromRoomGroupID(groupID string) (string, bool) {
	groupID = strings.TrimSpace(groupID)
	if !roomid.ValidStringID(groupID) {
		return "", false
	}
	return groupID, true
}

func parseBroadcastGroupID(groupID string) ParsedGroup {
	// v2 后缀必须先于 v1 判断：v2 群和 v1 群解析为同一个 Kind，调用方不感知版本。
	if strings.HasPrefix(groupID, "hy_") && strings.HasSuffix(groupID, globalSuffixV2) {
		app := strings.TrimSuffix(strings.TrimPrefix(groupID, "hy_"), globalSuffixV2)
		if validateAppCodeForGroupID(app) == nil {
			return ParsedGroup{Kind: KindGlobalBroadcast, AppCode: app}
		}
		return ParsedGroup{Kind: KindUnknown}
	}
	if strings.HasPrefix(groupID, "hy_") && strings.HasSuffix(groupID, globalSuffix) {
		app := strings.TrimSuffix(strings.TrimPrefix(groupID, "hy_"), globalSuffix)
		if validateAppCodeForGroupID(app) == nil {
			return ParsedGroup{Kind: KindGlobalBroadcast, AppCode: app}
		}
		return ParsedGroup{Kind: KindUnknown}
	}

	if after, ok := strings.CutPrefix(groupID, "hy_"); ok {
		body := after
		// v2 片段必须先于 v1 判断：v1 的 `_bc_r_` 是 v2 `_bc_r_v2_` 的前缀，
		// 先按 v1 解析会把 v2 群的 `v2_<id>` 当成非法区域。两个版本解析为同一个 Kind，调用方不感知版本。
		if parsed, matched := parseRegionBroadcastBody(body, regionTokenV2); matched {
			return parsed
		}
		if parsed, matched := parseRegionBroadcastBody(body, regionToken); matched {
			return parsed
		}
	}

	return ParsedGroup{Kind: KindUnknown}
}

func parseRegionBroadcastBody(body string, token string) (ParsedGroup, bool) {
	tokenIndex := strings.LastIndex(body, token)
	if tokenIndex < 0 {
		return ParsedGroup{Kind: KindUnknown}, false
	}
	app := body[:tokenIndex]
	rawRegion := body[tokenIndex+len(token):]
	regionID, err := strconv.ParseInt(rawRegion, 10, 64)
	if err == nil && regionID > 0 && validateAppCodeForGroupID(app) == nil {
		return ParsedGroup{Kind: KindRegionBroadcast, AppCode: app, RegionID: regionID}, true
	}
	return ParsedGroup{Kind: KindUnknown}, true
}

func validateGroupIDPrefix(prefix string) error {
	if prefix == "" {
		return nil
	}
	for i := 0; i < len(prefix); i++ {
		char := prefix[i]
		if char >= 'a' && char <= 'z' {
			continue
		}
		if char >= 'A' && char <= 'Z' {
			continue
		}
		if char >= '0' && char <= '9' {
			continue
		}
		if char == '_' || char == '-' {
			continue
		}
		return fmt.Errorf("group_id_prefix contains unsupported character")
	}
	return nil
}

func validateAppCodeForGroupID(app string) error {
	if app == "" {
		return fmt.Errorf("app_code is required")
	}
	// GroupID 是腾讯云侧长期存在的外部标识，只允许稳定 ASCII 子集，避免大小写、空格或 Unicode 造成不可逆映射。
	for i := 0; i < len(app); i++ {
		char := app[i]
		if char >= 'a' && char <= 'z' {
			continue
		}
		if char >= '0' && char <= '9' {
			continue
		}
		if char == '_' || char == '-' {
			continue
		}
		return fmt.Errorf("app_code contains unsupported character")
	}
	return nil
}
