package xerr

import (
	"errors"
	"fmt"
	"strings"
)

// Code 是服务内部稳定错误原因。
// 它对应 google.rpc.ErrorInfo.reason，而不是 HTTP code 或 gRPC code。
type Code string

const (
	// InvalidArgument 表示请求字段、格式或领域约束不合法。
	InvalidArgument Code = "INVALID_ARGUMENT"
	// NotFound 表示目标实体不存在。
	NotFound Code = "NOT_FOUND"
	// Conflict 表示唯一键、版本、幂等或状态冲突。
	Conflict Code = "CONFLICT"
	// RoomClosed 表示房间已被后台关闭，客户端不能再进入。
	RoomClosed Code = "ROOM_CLOSED"
	// RoomUserMuted 表示房间禁言正在阻止用户上麦、发流或重新打开麦克风。
	RoomUserMuted Code = "ROOM_USER_MUTED"
	// RoomRPSChallengeTaken 表示房内猜拳挑战已被其他用户接单，当前用户不能再重复应战。
	RoomRPSChallengeTaken Code = "ROOM_RPS_CHALLENGE_TAKEN"
	// RoomRPSChallengeExpired 表示房内猜拳挑战已越过无人应战截止时间，客户端应关闭应战入口并提示过期。
	RoomRPSChallengeExpired Code = "ROOM_RPS_CHALLENGE_EXPIRED"
	// Unauthorized 表示身份不存在或认证失败。
	Unauthorized Code = "UNAUTHORIZED"
	// PermissionDenied 表示已认证但没有执行权限。
	PermissionDenied Code = "PERMISSION_DENIED"
	// ProfileRequired 表示已登录用户尚未完成 App 最小注册资料，不能进入门禁保护业务。
	ProfileRequired Code = "PROFILE_REQUIRED"
	// Unavailable 表示依赖或当前服务临时不可用。
	Unavailable Code = "UNAVAILABLE"
	// Internal 表示不能安全暴露更细原因的内部失败。
	Internal Code = "INTERNAL_ERROR"

	// AuthFailed 统一短号不存在、未设置密码、密码错误和三方凭证拒绝，避免身份枚举。
	AuthFailed Code = "AUTH_FAILED"
	// AuthLoginBlocked 表示登录入口命中地区、语言、时区或 IP 风控策略。
	AuthLoginBlocked Code = "AUTH_LOGIN_BLOCKED"
	// PasswordAlreadySet 表示用户已经设置过密码，不能重复初始化密码身份。
	PasswordAlreadySet Code = "PASSWORD_ALREADY_SET"
	// UserDisabled 表示用户被禁用或封禁。
	UserDisabled Code = "USER_DISABLED"
	// SessionExpired 表示 refresh token 或会话已过期。
	SessionExpired Code = "SESSION_EXPIRED"
	// SessionRevoked 表示 refresh token 或会话已被主动失效。
	SessionRevoked Code = "SESSION_REVOKED"
	// DisplayUserIDInvalid 表示短号格式不符合 user-service 规则。
	DisplayUserIDInvalid Code = "DISPLAY_USER_ID_INVALID"
	// DisplayUserIDExists 表示短号已被 active 用户占用。
	DisplayUserIDExists Code = "DISPLAY_USER_ID_EXISTS"
	// DisplayUserIDCooldown 表示用户仍处在短号修改冷却期。
	DisplayUserIDCooldown Code = "DISPLAY_USER_ID_COOLDOWN"
	// DisplayUserIDNotFound 表示 active 短号不存在。
	DisplayUserIDNotFound Code = "DISPLAY_USER_ID_NOT_FOUND"
	// DisplayUserIDAllocateFailed 表示默认短号分配重试耗尽。
	DisplayUserIDAllocateFailed Code = "DISPLAY_USER_ID_ALLOCATE_FAILED"
	// DisplayUserIDPrettyNotAvailable 表示靓号不可申请、已被租用或被预留。
	DisplayUserIDPrettyNotAvailable Code = "DISPLAY_USER_ID_PRETTY_NOT_AVAILABLE"
	// DisplayUserIDPrettyActive 表示用户已有 active 靓号租约。
	DisplayUserIDPrettyActive Code = "DISPLAY_USER_ID_PRETTY_ACTIVE"
	// DisplayUserIDLeaseExpired 表示靓号租约已经过期或不能再被当前操作使用。
	DisplayUserIDLeaseExpired Code = "DISPLAY_USER_ID_LEASE_EXPIRED"
	// DisplayUserIDPaymentRequired 表示申请靓号缺少有效支付凭证。
	DisplayUserIDPaymentRequired Code = "DISPLAY_USER_ID_PAYMENT_REQUIRED"
	// CountryChangeCooldown 表示用户仍处在国家修改冷却期。
	CountryChangeCooldown Code = "COUNTRY_CHANGE_COOLDOWN"
	// CountryChangeIdentityLocked 表示用户已有经营身份，国家只能由客服治理入口修改。
	CountryChangeIdentityLocked Code = "COUNTRY_CHANGE_IDENTITY_LOCKED"
	// CountryNotFound 表示国家主数据不存在或已经停用。
	CountryNotFound Code = "COUNTRY_NOT_FOUND"
	// RegionNotFound 表示区域主数据不存在。
	RegionNotFound Code = "REGION_NOT_FOUND"
	// RegionCountryConflict 表示国家已经归属其他 active 区域。
	RegionCountryConflict Code = "REGION_COUNTRY_CONFLICT"
	// RegionDisabled 表示区域已经停用，不能继续配置国家。
	RegionDisabled Code = "REGION_DISABLED"
	// RegionMismatch 表示当前动作要求双方同一区域，但用户、组织或上级身份的实时区域不一致。
	RegionMismatch Code = "REGION_MISMATCH"
	// InvalidInviteCode 表示邀请码不存在、停用、跨 App、邀请人不可用或用户尝试自邀。
	InvalidInviteCode Code = "INVALID_INVITE_CODE"
	// DeviceAlreadyRegistered 表示同一注册设备的账号数量已经达到注册上限，不能继续创建新账号。
	DeviceAlreadyRegistered Code = "DEVICE_ALREADY_REGISTERED"
	// CPAlreadyExistsSelf 表示当前点击同意申请的用户已经有 active CP。
	CPAlreadyExistsSelf Code = "CP_ALREADY_EXISTS_SELF"
	// CPAlreadyExistsOther 表示申请另一方已经有 active CP。
	CPAlreadyExistsOther Code = "CP_ALREADY_EXISTS_OTHER"

	// InsufficientBalance 表示钱包余额不足。
	InsufficientBalance Code = "INSUFFICIENT_BALANCE"
	// DuplicateBillingCommand 表示账务幂等键命中已处理命令。
	DuplicateBillingCommand Code = "DUPLICATE_BILLING_COMMAND"
	// LedgerConflict 表示账本并发写入或余额版本冲突。
	LedgerConflict Code = "LEDGER_CONFLICT"
	// WalletResourceReadTransient 表示钱包资源读模型遇到可安全重试的瞬时数据库冲突。
	WalletResourceReadTransient Code = "WALLET_RESOURCE_READ_TRANSIENT"
	// CoinSellerNotActive 表示目标用户不存在 active 币商身份。
	CoinSellerNotActive Code = "COIN_SELLER_NOT_ACTIVE"
	// CoinSellerStockTypeInvalid 表示币商库存入账类型不属于支持范围。
	CoinSellerStockTypeInvalid Code = "COIN_SELLER_STOCK_TYPE_INVALID"
	// CoinSellerStockAmountInvalid 表示币商库存金币数或线下付款金额不合法。
	CoinSellerStockAmountInvalid Code = "COIN_SELLER_STOCK_AMOUNT_INVALID"
	// CoinSellerPaymentRefDuplicated 表示同一 USDT 收款凭证已经入账。
	CoinSellerPaymentRefDuplicated Code = "COIN_SELLER_PAYMENT_REF_DUPLICATED"
	// IdempotencyConflict 表示 command_id 相同但业务 payload 不一致。
	IdempotencyConflict Code = "IDEMPOTENCY_CONFLICT"
	// PointWithdrawalLimitReached 表示用户已达到月工资政策配置的 UTC 日/周/月提现次数上限。
	PointWithdrawalLimitReached Code = "POINT_WITHDRAWAL_LIMIT_REACHED"
	// PointWithdrawalDateNotAllowed 表示当前 UTC 日不在工资政策允许的平台提现日期内。
	PointWithdrawalDateNotAllowed Code = "POINT_WITHDRAWAL_DATE_NOT_ALLOWED"
	// VIPLevelNotFound 表示目标 VIP 等级不存在。
	VIPLevelNotFound Code = "VIP_LEVEL_NOT_FOUND"
	// VIPLevelDisabled 表示目标 VIP 等级当前不可购买。
	VIPLevelDisabled Code = "VIP_LEVEL_DISABLED"
	// VIPDowngradeNotAllowed 表示用户当前有效 VIP 等级高于购买或体验卡的目标等级。
	VIPDowngradeNotAllowed Code = "VIP_DOWNGRADE_NOT_ALLOWED"
	// VIPProgramInactive 表示当前 App 未启用可执行的 VIP 体系。
	VIPProgramInactive Code = "VIP_PROGRAM_INACTIVE"
	// VIPPackageNotPurchasable 表示目标套餐不存在、停用或不在当前体系等级范围内。
	VIPPackageNotPurchasable Code = "VIP_PACKAGE_NOT_PURCHASABLE"
	// VIPInsufficientCoin 表示 VIP 购买事务中的 COIN 余额不足。
	VIPInsufficientCoin Code = "VIP_INSUFFICIENT_COIN"
	// VIPTrialCardNotFound 表示体验卡实例不存在、不属于当前用户或其背包权益已撤销。
	VIPTrialCardNotFound Code = "VIP_TRIAL_CARD_NOT_FOUND"
	// VIPTrialCardExpired 表示体验卡或对应背包权益已越过绝对过期时间。
	VIPTrialCardExpired Code = "VIP_TRIAL_CARD_EXPIRED"
	// VIPBenefitRequired 表示当前操作需要一项未生效或已被用户关闭的 VIP 权益。
	VIPBenefitRequired Code = "VIP_BENEFIT_REQUIRED"
	// VIPGiftLevelRequired 表示发送者当前生效 VIP 等级低于礼物配置要求。
	VIPGiftLevelRequired Code = "VIP_GIFT_LEVEL_REQUIRED"
	// VIPAntiKick 表示普通房主/管理员踢人动作被目标用户的防踢权益拦截。
	VIPAntiKick Code = "VIP_ANTI_KICK"
	// VIPAntiMute 表示普通房主/管理员禁言动作被目标用户的防禁言权益拦截。
	VIPAntiMute Code = "VIP_ANTI_MUTE"
	// VIPRechargeRequired 表示目标 VIP 等级需要用户先达到累计充值门槛。
	VIPRechargeRequired Code = "VIP_RECHARGE_REQUIRED"
	// VIPCoinRebateNotFound 表示返现不存在，或不属于当前 App/用户。
	VIPCoinRebateNotFound Code = "VIP_COIN_REBATE_NOT_FOUND"
	// VIPCoinRebateExpired 表示返现已越过 UTC 次日日切的排他结束边界。
	VIPCoinRebateExpired Code = "VIP_COIN_REBATE_EXPIRED"

	// RedPacketDisabled 表示红包功能未启用。
	RedPacketDisabled Code = "RED_PACKET_DISABLED"
	// RedPacketInvalidAmountTier 表示红包金额不在后台配置档位内。
	RedPacketInvalidAmountTier Code = "INVALID_AMOUNT_TIER"
	// RedPacketInvalidCountTier 表示红包数量不在后台配置档位内。
	RedPacketInvalidCountTier Code = "INVALID_COUNT_TIER"
	// RedPacketAmountTooSmall 表示红包总金额小于红包份数，无法保证每份至少 1 金币。
	RedPacketAmountTooSmall Code = "RED_PACKET_AMOUNT_TOO_SMALL"
	// RedPacketDailyLimitReached 表示用户达到 UTC 当日发送次数上限。
	RedPacketDailyLimitReached Code = "DAILY_SEND_LIMIT_REACHED"
	// RedPacketNotOpen 表示延迟红包尚未到打开时间。
	RedPacketNotOpen Code = "RED_PACKET_NOT_OPEN"
	// RedPacketExpired 表示红包已经过期。
	RedPacketExpired Code = "RED_PACKET_EXPIRED"
	// RedPacketAlreadyClaimed 表示当前用户已领取过该红包。
	RedPacketAlreadyClaimed Code = "RED_PACKET_ALREADY_CLAIMED"
	// RedPacketSoldOut 表示红包已经被抢完。
	RedPacketSoldOut Code = "RED_PACKET_SOLD_OUT"

	// RuleNotActive 表示活动规则当前不可用。
	RuleNotActive Code = "RULE_NOT_ACTIVE"
	// EventAlreadyConsumed 表示活动事件消费幂等命中。
	EventAlreadyConsumed Code = "EVENT_ALREADY_CONSUMED"
	// RewardPending 表示奖励仍在处理中，不能重复结算。
	RewardPending Code = "REWARD_PENDING"

	// InvalidSection 表示 App 消息分区不属于 system/activity。
	InvalidSection Code = "INVALID_SECTION"
	// MessageNotFound 表示消息不存在、已删除、已过期、已撤回或不属于当前用户。
	MessageNotFound Code = "MESSAGE_NOT_FOUND"
	// MessageRecalled 表示消息已撤回，不能继续展示或操作。
	MessageRecalled Code = "MESSAGE_RECALLED"
	// PageTokenInvalid 表示分页游标不能解析或不属于当前查询。
	PageTokenInvalid Code = "PAGE_TOKEN_INVALID"
	// RequestConflict 表示同 command_id 的 fanout 请求 payload 冲突。
	RequestConflict Code = "REQUEST_CONFLICT"
	// ProducerEventConflict 表示同 producer_event_id 的入箱请求 payload 冲突。
	ProducerEventConflict Code = "PRODUCER_EVENT_CONFLICT"
)

// Error 是 domain/service 层统一返回的业务错误。
// 该类型不依赖任何传输层包，避免业务代码直接绑定 gRPC status。
type Error struct {
	Code    Code
	Message string
	// Metadata 只保存业务层显式声明可跨服务、可返回客户端的短字段。
	// 原始错误、SQL、配置和第三方响应不得写入这里；ToGRPCError 会把它投影到
	// google.rpc.ErrorInfo.metadata，gateway 再原样写入 envelope.data。
	Metadata map[string]string
}

// Error 实现标准 error 接口。
func (e *Error) Error() string {
	return fmt.Sprintf("%s: %s", e.Code, e.Message)
}

// Is 让 errors.Is 可以按稳定 reason 判断业务错误。
func (e *Error) Is(target error) bool {
	typed, ok := target.(*Error)
	return ok && e.Code == typed.Code
}

// New 构造带稳定 reason 的业务错误。
func New(code Code, message string) error {
	return &Error{
		Code:    code,
		Message: message,
	}
}

// NewWithMetadata 构造带安全结构化详情的业务错误。
// metadata 是显式的 wire contract，不接受空键；键和值都会去除首尾空白并复制，
// 防止调用方后续修改 map 导致同一个错误在日志与 wire 上出现不同语义。
func NewWithMetadata(code Code, message string, metadata map[string]string) error {
	return &Error{
		Code:     code,
		Message:  message,
		Metadata: normalizeMetadata(metadata),
	}
}

// As 提取当前仓库约定的业务错误。
func As(err error) (*Error, bool) {
	if err == nil {
		return nil, false
	}

	var typed *Error
	if errors.As(err, &typed) {
		return typed, true
	}

	return nil, false
}

// CodeOf 安全提取错误 reason。
func CodeOf(err error) Code {
	if typed, ok := As(err); ok {
		return typed.Code
	}

	if err == nil {
		return ""
	}

	return Internal
}

// MessageOf 安全提取可返回给内部调用方的短错误描述。
func MessageOf(err error) string {
	if typed, ok := As(err); ok {
		return typed.Message
	}

	if err == nil {
		return ""
	}

	return "internal error"
}

// MetadataOf 返回业务错误显式声明的安全 metadata 副本。
// 未识别错误没有可安全暴露的结构化详情，必须返回 nil 而不是猜测原始错误内容。
func MetadataOf(err error) map[string]string {
	if typed, ok := As(err); ok {
		return cloneMetadata(typed.Metadata)
	}
	return nil
}

func normalizeMetadata(metadata map[string]string) map[string]string {
	if len(metadata) == 0 {
		return nil
	}
	normalized := make(map[string]string, len(metadata))
	for key, value := range metadata {
		key = strings.TrimSpace(key)
		if key == "" {
			continue
		}
		normalized[key] = strings.TrimSpace(value)
	}
	if len(normalized) == 0 {
		return nil
	}
	return normalized
}

func cloneMetadata(metadata map[string]string) map[string]string {
	if len(metadata) == 0 {
		return nil
	}
	cloned := make(map[string]string, len(metadata))
	for key, value := range metadata {
		cloned[key] = value
	}
	return cloned
}
