syntax = "proto3";

package hyapp.luckygift.v1;

option go_package = "hyapp.local/api/proto/luckygift/v1;luckygiftv1";

// RequestMeta 是 lucky-gift-service 的内部追踪元信息。
// request_id 只用于链路排查，不能被任何业务流程当成幂等键使用。
message RequestMeta {
  string request_id = 1;
  string caller = 2;
  string gateway_node_id = 3;
  int64 sent_at_ms = 4;
  string app_code = 5;
}

// LuckyGiftMeta 是 HyApp 内部房间送礼扣费成功后的抽奖事实输入。
// 金额、目标用户和 command_id 必须来自 wallet-service 已落账的 receipt，不能从客户端 payload 推导。
message LuckyGiftMeta {
  RequestMeta meta = 1;
  string command_id = 2;
  int64 user_id = 3;
  string device_id = 4;
  string room_id = 5;
  string anchor_id = 6;
  string gift_id = 7;
  int64 coin_spent = 8;
  int64 paid_at_ms = 9;
  string pool_id = 10;
  int64 target_user_id = 11;
  int32 gift_count = 12;
  int64 visible_region_id = 13;
  int64 country_id = 14;
  // sender_* 是 room-service 在送礼入口已经确认的展示快照，只进入中奖事实和 IM，不能参与抽奖或账务判断。
  string sender_name = 15;
  string sender_avatar = 16;
  string sender_display_user_id = 17;
  string sender_pretty_display_user_id = 18;
  // 充值分层与充值后短时加权必须由调用方传入已确认的用户事实；幸运礼物服务不跨库回查用户或支付明细。
  int64 recharge_7d_coins = 19;
  int64 recharge_30d_coins = 20;
  int64 last_recharged_at_ms = 21;
  // gift_income_coins 是滚动兼容保留的旧字段名，实际承载 wallet 本次按 Host 政策结算的周期钻石；
  // 已退役的直接 POINT 回执不再参与拆分，Agency 额外收益也不包含在内。
  // 幸运礼物服务只能消费该 owner 回执，不能再用幸运礼物规则里的主播比例覆盖它。
  int64 gift_income_coins = 22;
  // user_registered_at_ms 是 user-service 在送礼入口返回的账号创建事实快照。
  // dynamic_v3 只用它判断闭窗时账号是否已满 48 小时；缺失时普通开奖继续，但补偿大奖资格 fail-close。
  int64 user_registered_at_ms = 23;
  // room_locked 由 Room Cell 在送礼命令串行执行期间读取，只进入中奖展示事实，不参与抽奖或账务计算。
  bool room_locked = 24;
  // owner_user_id 与 room_locked 在同一 Room Cell 串行区间固化；lucky owner 只持久化并透传该展示快照。
  int64 owner_user_id = 25;
}

message LuckyGiftRuleTier {
  string stage = 1;
  string tier_id = 2;
  int64 multiplier_ppm = 3;
  int64 base_weight_ppm = 4;
  bool high_water_only = 5;
  bool enabled = 6;
}

message LuckyGiftRuleStage {
  string stage = 1;
  repeated LuckyGiftRuleTier tiers = 2;
  // 两个充值门槛共同决定 dynamic_v3 分层；novice 固定为 0/0，后续阶段逐维单调递增。
  int64 min_recharge_7d_coins = 3;
  int64 min_recharge_30d_coins = 4;
}

message LuckyGiftRuleConfig {
  string app_code = 1;
  string pool_id = 2;
  int64 rule_version = 3;
  bool enabled = 4;
  int64 target_rtp_ppm = 5;
  int64 pool_rate_ppm = 6;
  int64 settlement_window_wager = 7;
  int64 control_band_ppm = 8;
  int64 gift_price_reference = 9;
  int64 novice_max_equivalent_draws = 10;
  int64 normal_max_equivalent_draws = 11;
  int64 effective_from_ms = 12;
  int64 created_by_admin_id = 13;
  int64 created_at_ms = 14;
  repeated LuckyGiftRuleStage stages = 15;
  // strategy_version 隔离历史 fixed_v2 与动态算法 dynamic_v3，避免新增约束破坏已经发布的旧规则。
  string strategy_version = 16;
  int64 profit_rate_ppm = 17;
  int64 anchor_rate_ppm = 18;
  int64 initial_pool_coins = 19;
  int64 loss_streak_guarantee = 20;
  int64 low_watermark_coins = 21;
  int64 low_water_nonzero_factor_ppm = 22;
  int64 high_watermark_coins = 23;
  int64 high_water_nonzero_factor_ppm = 24;
  int64 recharge_boost_window_ms = 25;
  int64 recharge_boost_factor_ppm = 26;
  repeated int64 jackpot_multiplier_ppms = 27;
  int64 jackpot_global_rtp_max_ppm = 28;
  int64 jackpot_user_round_rtp_max_ppm = 29;
  int64 jackpot_user_48h_rtp_max_ppm = 30;
  // 用户在同一应用、奖池和 UTC 自然日每累计消费满该金币数，生成一次可跨日保留的消费大奖资格。
  int64 jackpot_spend_threshold_coins = 31;
  int64 max_jackpot_hits_per_user_day = 32;
  int64 max_single_payout = 33;
  int64 user_hourly_payout_cap = 34;
  int64 user_daily_payout_cap = 35;
  int64 device_daily_payout_cap = 36;
  int64 room_hourly_payout_cap = 37;
  int64 anchor_daily_payout_cap = 38;
  // 用户严格滚动 24 小时 RTP 超过此值时，普通非零奖档按下方因子降权；0 表示关闭该策略。
  int64 user_24h_rtp_threshold_ppm = 39;
  // 触发用户 24 小时 RTP 降权后保留的普通中奖权重；1000000 表示保持原概率。
  int64 user_24h_ordinary_win_factor_ppm = 40;
  // fixed_v2 高水位返奖加权：奖池水位越过阈值后中奖返奖按系数放大（RTP 等比放大），
  // 滞回保持到水位跌破恢复线才退出。0 表示关闭；dynamic_v3 必须保持三个字段为 0。
  int64 v2_high_water_boost_threshold_coins = 41;
  // 加权系数 ppm；1500000 表示 RTP 变为原值的 150%。启用时必须大于 1000000。
  int64 v2_high_water_boost_factor_ppm = 42;
  // 恢复水位金币；水位低于该值后退出加权。启用时必须为正且不高于阈值。
  int64 v2_high_water_boost_recover_coins = 43;
  // app+pool 全局滚动爆奖窗口；RTP 补偿和累计消费大奖共用，所有规则版本也共用实际命中事实。
  int64 jackpot_rate_limit_window_minutes = 44;
  // 上述滚动窗口内最多允许的大奖次数；达到上限后消费 token 保留，RTP 补偿资格按本次已尝试处理。
  int64 max_jackpot_hits_per_window = 45;
}

message CheckLuckyGiftRequest {
  RequestMeta meta = 1;
  int64 user_id = 2;
  string room_id = 3;
  string gift_id = 4;
  string pool_id = 5;
  // 可选的可信设备 claim。旧 token 缺失时保持空值；实验分组只按用户键，不得按设备改变组别。
  string device_id = 6;
}

message CheckLuckyGiftResponse {
  bool enabled = 1;
  string reason = 2;
  string gift_id = 3;
  int64 gift_price = 4;
  int64 rule_version = 5;
  int64 target_rtp_ppm = 6;
  string experience_pool = 7;
  string pool_id = 8;
  // strategy_version 由 lucky-gift owner 返回并由 room saga 固化，用于滚动发布时区分 fixed_v2 兼容与 dynamic_v3 强校验。
  string strategy_version = 9;
}

message LuckyGiftDrawResult {
  string draw_id = 1;
  string command_id = 2;
  string gift_id = 3;
  int64 rule_version = 4;
  string experience_pool = 5;
  string selected_tier_id = 6;
  int64 base_reward_coins = 7;
  int64 effective_reward_coins = 8;
  string reward_status = 9;
  int64 rtp_window_index = 10;
  int64 gift_rtp_window_index = 11;
  int64 global_base_rtp_ppm = 12;
  int64 gift_base_rtp_ppm = 13;
  bool stage_feedback = 14;
  bool high_multiplier = 15;
  int64 created_at_ms = 16;
  string pool_id = 17;
  int64 multiplier_ppm = 18;
  string wallet_transaction_id = 19;
  int64 coin_balance_after = 20;
  int64 user_id = 21;
  string external_user_id = 22;
  string app_code = 23;
  repeated LuckyGiftHit hits = 24;
}

message ExecuteLuckyGiftDrawRequest {
  LuckyGiftMeta lucky_gift = 1;
}

message ExecuteLuckyGiftDrawResponse {
  LuckyGiftDrawResult result = 1;
}

message BatchExecuteLuckyGiftDrawRequest {
  repeated LuckyGiftMeta lucky_gifts = 1;
}

message BatchExecuteLuckyGiftDrawResponse {
  repeated LuckyGiftDrawResult results = 1;
}

// ExternalGiftDrawRequest 是外部 App 接入的抽奖输入。
// App 调用前必须已经完成自己的扣费，lucky-gift-service 只校验金额事实并返回中奖结果。
message ExternalGiftDrawRequest {
  RequestMeta meta = 1;
  string app_code = 3;
  string external_user_id = 4;
  string request_id = 5;
  int64 gift_count = 6;
  int64 unit_amount = 7;
  int64 total_amount = 8;
  string currency = 9;
  int64 paid_at_ms = 10;
  string metadata_json = 11;
  string pool_id = 12;
  // dynamic_v3 必填的稳定设备作用域。luck-gateway 不独立证明其真实性，调用方必须在自己的认证/签名边界内绑定该值；fixed_v2 允许缺失。
  string device_id = 13;
  // user_registered_at_ms 由外部 App 按自己的用户主数据提供；dynamic_v3 缺失或晚于支付时间时补偿大奖资格 fail-close。
  int64 user_registered_at_ms = 14;
}

message ExternalGiftDrawResponse {
  string draw_id = 1;
  string request_id = 2;
  string app_code = 4;
  string external_user_id = 5;
  int64 gift_count = 6;
  int64 unit_amount = 7;
  int64 total_amount = 8;
  int64 reward_amount = 9;
  int64 multiplier_ppm = 10;
  string reward_status = 11;
  int64 rule_version = 12;
  int64 created_at_ms = 13;
}

message GetLuckyGiftConfigRequest {
  RequestMeta meta = 1;
  string pool_id = 2;
}

message GetLuckyGiftConfigResponse {
  LuckyGiftRuleConfig config = 1;
}

message UpsertLuckyGiftConfigRequest {
  RequestMeta meta = 1;
  LuckyGiftRuleConfig config = 2;
  int64 operator_admin_id = 3;
}

message UpsertLuckyGiftConfigResponse {
  LuckyGiftRuleConfig config = 1;
}

message ListLuckyGiftConfigsRequest {
  RequestMeta meta = 1;
}

message ListLuckyGiftConfigsResponse {
  repeated LuckyGiftRuleConfig configs = 1;
}

// ListLuckyGiftConfigVersions 只读取同一 app+pool 的不可变历史快照；返回顺序为版本号倒序。
message ListLuckyGiftConfigVersionsRequest {
  RequestMeta meta = 1;
  string pool_id = 2;
}

message ListLuckyGiftConfigVersionsResponse {
  repeated LuckyGiftRuleConfig configs = 1;
}

// RollbackLuckyGiftConfig 不覆盖历史行，而是把目标快照复制成一个新的最新版本并立即生效。
message RollbackLuckyGiftConfigRequest {
  RequestMeta meta = 1;
  string pool_id = 2;
  int64 target_rule_version = 3;
  int64 operator_admin_id = 4;
}

message RollbackLuckyGiftConfigResponse {
  int64 source_rule_version = 1;
  LuckyGiftRuleConfig config = 2;
}

message ListLuckyGiftDrawsRequest {
  RequestMeta meta = 1;
  string gift_id = 2;
  int64 user_id = 3;
  string room_id = 4;
  string status = 5;
  int32 page = 6;
  int32 page_size = 7;
  string pool_id = 8;
  string external_user_id = 10;
  bool external_only = 11;
}

message ListLuckyGiftDrawsResponse {
  repeated LuckyGiftDrawResult draws = 1;
  int64 total = 2;
}

message LuckyGiftDrawSummary {
  string pool_id = 1;
  int64 total_draws = 2;
  int64 unique_users = 3;
  int64 unique_rooms = 4;
  int64 total_spent_coins = 5;
  int64 total_reward_coins = 6;
  int64 base_reward_coins = 7;
  int64 actual_rtp_ppm = 8;
  int64 pending_draws = 9;
  int64 granted_draws = 10;
  int64 failed_draws = 11;
}

message LuckyGiftPoolBalance {
  string app_code = 1;
  string pool_id = 2;
  int64 balance = 3;
  int64 reserve_floor = 4;
  int64 available_balance = 5;
  int64 total_in = 6;
  int64 total_out = 7;
  bool materialized = 8;
  int64 updated_at_ms = 9;
  string strategy_version = 10;
  int64 manual_credit_total = 11;
  int64 manual_debit_total = 12;
}

message GetLuckyGiftDrawSummaryRequest {
  RequestMeta meta = 1;
  string gift_id = 2;
  int64 user_id = 3;
  string room_id = 4;
  string status = 5;
  string pool_id = 6;
  string external_user_id = 8;
  bool external_only = 9;
}

message GetLuckyGiftDrawSummaryResponse {
  LuckyGiftDrawSummary summary = 1;
}

message ListLuckyGiftPoolBalancesRequest {
  RequestMeta meta = 1;
  string pool_id = 2;
  string strategy_version = 3;
}

message ListLuckyGiftPoolBalancesResponse {
  repeated LuckyGiftPoolBalance pools = 1;
}

// LuckyGiftExperimentOverride 把单个用户强制固定到某个实验组，用于放量前的白名单验证。
// user_key：内部用户为十进制 user_id 字符串；外部用户为 "ext:" + external_user_id。
message LuckyGiftExperimentOverride {
  string user_key = 1;
  string arm = 2;
}

// LuckyGiftExperimentArmStats 是实验开始以来单组的抽奖聚合，由 lucky_draw_records 按 rule_version 现算，非物化口径。
message LuckyGiftExperimentArmStats {
  string arm = 1;
  int64 rule_version = 2;
  string strategy_version = 3;
  int64 total_draws = 4;
  int64 unique_users = 5;
  int64 total_spent_coins = 6;
  int64 total_reward_coins = 7;
  int64 actual_rtp_ppm = 8;
}

// LuckyGiftExperiment 是单奖池 AB 实验事实：两组各钉住一个不可变 rule_version，
// 运行时按 bucket_salt+user 哈希分流；同一奖池同时最多一个未完成实验。
message LuckyGiftExperiment {
  string app_code = 1;
  string pool_id = 2;
  string experiment_id = 3;
  string name = 4;
  int64 control_rule_version = 5;
  int64 treatment_rule_version = 6;
  // treatment 组流量比例（ppm，0..1000000）；0 表示只有白名单 override 能进入 treatment。
  int64 treatment_traffic_ppm = 7;
  string bucket_salt = 8;
  // running / paused / completed；paused 时全部流量（含白名单）回落 control。
  string status = 9;
  string winner_arm = 10;
  int64 winner_rule_version = 11;
  // 结束实验时把获胜配置重发布成的新最新版本，全量流量由它承接。
  int64 final_rule_version = 12;
  int64 created_by_admin_id = 13;
  int64 started_at_ms = 14;
  int64 ended_at_ms = 15;
  int64 updated_at_ms = 16;
  repeated LuckyGiftExperimentOverride overrides = 17;
  repeated LuckyGiftExperimentArmStats arm_stats = 18;
}

// CreateLuckyGiftExperimentRequest 在一个事务里发布 treatment 新版本并登记实验，
// 避免"先发布后建实验"窗口期内新版本被最新版本语义全量放开。
// control 组自动钉住当前最新已启用版本。
message CreateLuckyGiftExperimentRequest {
  RequestMeta meta = 1;
  string name = 2;
  LuckyGiftRuleConfig treatment_config = 3;
  int64 treatment_traffic_ppm = 4;
  int64 operator_admin_id = 5;
  repeated LuckyGiftExperimentOverride overrides = 6;
}

message CreateLuckyGiftExperimentResponse {
  LuckyGiftExperiment experiment = 1;
  LuckyGiftRuleConfig treatment_config = 2;
}

message ListLuckyGiftExperimentsRequest {
  RequestMeta meta = 1;
  string pool_id = 2;
  string status = 3;
  bool with_stats = 4;
}

message ListLuckyGiftExperimentsResponse {
  repeated LuckyGiftExperiment experiments = 1;
}

// UpdateLuckyGiftExperimentRequest 调整放量或暂停/恢复。
// treatment_traffic_ppm 使用显式存在性语义：未设置表示不修改流量，避免 proto3 缺省 0 被误解释成"放量清零"；
// status 为空表示不修改状态。
message UpdateLuckyGiftExperimentRequest {
  RequestMeta meta = 1;
  string experiment_id = 2;
  optional int64 treatment_traffic_ppm = 3;
  string status = 4;
  int64 operator_admin_id = 5;
}

message UpdateLuckyGiftExperimentResponse {
  LuckyGiftExperiment experiment = 1;
}

message SetLuckyGiftExperimentOverridesRequest {
  RequestMeta meta = 1;
  string experiment_id = 2;
  repeated LuckyGiftExperimentOverride upserts = 3;
  repeated string removes = 4;
  int64 operator_admin_id = 5;
}

message SetLuckyGiftExperimentOverridesResponse {
  LuckyGiftExperiment experiment = 1;
}

// EndLuckyGiftExperimentRequest 结束实验：把 winner_arm 钉住的配置快照重发布为新的最新版本，
// 同事务内完成实验收口，保证结束瞬间的版本收敛没有中间态。
message EndLuckyGiftExperimentRequest {
  RequestMeta meta = 1;
  string experiment_id = 2;
  string winner_arm = 3;
  int64 operator_admin_id = 4;
}

message EndLuckyGiftExperimentResponse {
  LuckyGiftExperiment experiment = 1;
  LuckyGiftRuleConfig final_config = 2;
}

service LuckyGiftService {
  rpc CheckLuckyGift(CheckLuckyGiftRequest) returns (CheckLuckyGiftResponse);
  rpc ExecuteLuckyGiftDraw(ExecuteLuckyGiftDrawRequest) returns (ExecuteLuckyGiftDrawResponse);
  rpc BatchExecuteLuckyGiftDraw(BatchExecuteLuckyGiftDrawRequest) returns (BatchExecuteLuckyGiftDrawResponse);
  rpc ExecuteExternalGiftDraw(ExternalGiftDrawRequest) returns (ExternalGiftDrawResponse);
}

service AdminLuckyGiftService {
  rpc GetLuckyGiftConfig(GetLuckyGiftConfigRequest) returns (GetLuckyGiftConfigResponse);
  rpc UpsertLuckyGiftConfig(UpsertLuckyGiftConfigRequest) returns (UpsertLuckyGiftConfigResponse);
  rpc ListLuckyGiftConfigs(ListLuckyGiftConfigsRequest) returns (ListLuckyGiftConfigsResponse);
  rpc ListLuckyGiftConfigVersions(ListLuckyGiftConfigVersionsRequest) returns (ListLuckyGiftConfigVersionsResponse);
  rpc RollbackLuckyGiftConfig(RollbackLuckyGiftConfigRequest) returns (RollbackLuckyGiftConfigResponse);
  rpc ListLuckyGiftDraws(ListLuckyGiftDrawsRequest) returns (ListLuckyGiftDrawsResponse);
  rpc GetLuckyGiftDrawSummary(GetLuckyGiftDrawSummaryRequest) returns (GetLuckyGiftDrawSummaryResponse);
  rpc ListLuckyGiftPoolBalances(ListLuckyGiftPoolBalancesRequest) returns (ListLuckyGiftPoolBalancesResponse);
  rpc AdjustLuckyGiftPoolBalance(AdjustLuckyGiftPoolBalanceRequest) returns (AdjustLuckyGiftPoolBalanceResponse);
  rpc CreateLuckyGiftExperiment(CreateLuckyGiftExperimentRequest) returns (CreateLuckyGiftExperimentResponse);
  rpc ListLuckyGiftExperiments(ListLuckyGiftExperimentsRequest) returns (ListLuckyGiftExperimentsResponse);
  rpc UpdateLuckyGiftExperiment(UpdateLuckyGiftExperimentRequest) returns (UpdateLuckyGiftExperimentResponse);
  rpc SetLuckyGiftExperimentOverrides(SetLuckyGiftExperimentOverridesRequest) returns (SetLuckyGiftExperimentOverridesResponse);
  rpc EndLuckyGiftExperiment(EndLuckyGiftExperimentRequest) returns (EndLuckyGiftExperimentResponse);
  rpc ListLuckyGiftUserProfiles(ListLuckyGiftUserProfilesRequest) returns (ListLuckyGiftUserProfilesResponse);
  rpc GetLuckyGiftUserProfile(GetLuckyGiftUserProfileRequest) returns (GetLuckyGiftUserProfileResponse);
  rpc ListLuckyGiftDailyStats(ListLuckyGiftDailyStatsRequest) returns (ListLuckyGiftDailyStatsResponse);
}

// LuckyGiftHit 只返回批量逐份开奖中实际中奖的精简位置，不复制全部审计明细。
// 声明追加在文件尾以保持既有 protobuf message index 稳定。
message LuckyGiftHit {
  int32 gift_index = 1;
  string draw_id = 2;
  string selected_tier_id = 3;
  int64 multiplier_ppm = 4;
  int64 reward_coins = 5;
}

// 调账消息也只追加在文件尾，保持所有既有 protobuf message index 稳定。
// adjustment_id 是业务幂等键；RequestMeta.request_id 只做链路追踪，不能替代资金幂等语义。
message AdjustLuckyGiftPoolBalanceRequest {
  RequestMeta meta = 1;
  string pool_id = 2;
  string strategy_version = 3;
  string adjustment_id = 4;
  string direction = 5;
  int64 amount_coins = 6;
  string reason = 7;
  int64 operator_admin_id = 8;
}

message LuckyGiftPoolAdjustment {
  string adjustment_id = 1;
  string app_code = 2;
  string pool_id = 3;
  string strategy_version = 4;
  string direction = 5;
  int64 amount_coins = 6;
  string reason = 7;
  int64 operator_admin_id = 8;
  int64 balance_before = 9;
  int64 balance_after = 10;
  int64 created_at_ms = 11;
}

message AdjustLuckyGiftPoolBalanceResponse {
  LuckyGiftPoolAdjustment adjustment = 1;
  LuckyGiftPoolBalance pool = 2;
  bool idempotent_replay = 3;
}

// 用户画像协议追加在文件尾，避免改变既有 message index。外部策略哈希只存在于 owner 数据库，
// 本协议永远只下发 external_user_id，防止运营误把不可逆内部键当成外部用户身份。
message LuckyGiftUserProfileWindow {
  int64 draws = 1;
  int64 wager_coins = 2;
  int64 payout_coins = 3;
  int64 net_coins = 4;
  int64 rtp_ppm = 5;
  bool has_rtp = 6;
}

message LuckyGiftUserProfile {
  string app_code = 1;
  string pool_id = 2;
  string identity_type = 3;
  int64 internal_user_id = 4;
  string external_user_id = 5;
  int64 rule_version = 6;
  string strategy_version = 7;
  string stage = 8;
  int64 paid_draws = 9;
  int64 equivalent_draws = 10;
  int64 loss_streak = 11;
  int64 pending_spend_jackpot_tokens = 12;
  int64 guarantee_draws_remaining = 13;
  bool downweight_active = 14;
  int64 downweight_factor_ppm = 15;
  LuckyGiftUserProfileWindow one_hour = 16;
  LuckyGiftUserProfileWindow twenty_four_hours = 17;
  LuckyGiftUserProfileWindow forty_eight_hours = 18;
  LuckyGiftUserProfileWindow lifetime = 19;
  int64 base_reward_coins = 20;
  int64 ordinary_win_count = 21;
  int64 high_multiplier_ordinary_win_count = 22;
  int64 rtp_compensation_jackpot_count = 23;
  int64 cumulative_spend_jackpot_count = 24;
  int64 guarantee_hit_count = 25;
  int64 zero_draw_count = 26;
  int64 granted_draw_count = 27;
  int64 pending_draw_count = 28;
  int64 failed_draw_count = 29;
  int64 max_multiplier_ppm = 30;
  int64 max_reward_coins = 31;
  int64 first_draw_at_ms = 32;
  int64 last_draw_at_ms = 33;
  int64 aggregated_at_ms = 34;
  int64 data_lag_ms = 35;
  int64 user_24h_rtp_threshold_ppm = 36;
}

message ListLuckyGiftUserProfilesRequest {
  RequestMeta meta = 1;
  string pool_id = 2;
  string identity_type = 3;
  int64 internal_user_id = 4;
  string external_user_id = 5;
  string stage = 6;
  string status = 7;
  string sort_by = 8;
  string sort_direction = 9;
  int32 page = 10;
  int32 page_size = 11;
  // cursor 是 owner service 生成的不透明 keyset 游标；page 仅保留旧客户端协议兼容，服务端不再执行 OFFSET。
  string cursor = 12;
}

message ListLuckyGiftUserProfilesResponse {
  repeated LuckyGiftUserProfile profiles = 1;
  int64 total = 2;
  int64 snapshot_at_ms = 3;
  string next_cursor = 4;
  bool has_more = 5;
}

message GetLuckyGiftUserProfileRequest {
  RequestMeta meta = 1;
  string pool_id = 2;
  string identity_type = 3;
  int64 internal_user_id = 4;
  string external_user_id = 5;
  int32 jackpot_page = 6;
  int32 jackpot_page_size = 7;
  // jackpot_cursor 由上一页 next_jackpot_cursor 原样返回；jackpot_page 仅保留协议兼容。
  string jackpot_cursor = 8;
}

message LuckyGiftUserMultiplierStat {
  string hit_type = 1;
  int64 multiplier_ppm = 2;
  int64 draw_count = 3;
  int64 wager_coins = 4;
  int64 payout_coins = 5;
}

message LuckyGiftUserJackpotCondition {
  string name = 1;
  int64 numerator = 2;
  int64 denominator = 3;
  int64 ratio_ppm = 4;
  int64 limit_ppm = 5;
  int64 factor_ppm = 6;
  bool passed = 7;
  string reason = 8;
}

message LuckyGiftUserJackpotHit {
  string draw_id = 1;
  string request_id = 2;
  int64 rule_version = 3;
  string stage = 4;
  string mechanism = 5;
  string reason_code = 6;
  string reason_summary = 7;
  string tier_id = 8;
  int64 multiplier_ppm = 9;
  int64 wager_coins = 10;
  int64 payout_coins = 11;
  int64 occurred_at_ms = 12;
  repeated LuckyGiftUserJackpotCondition conditions = 13;
  // event_key 只标识这条画像大奖事实，供前端稳定追加去重；不包含用户策略键或用户哈希。
  string event_key = 14;
}

message GetLuckyGiftUserProfileResponse {
  LuckyGiftUserProfile profile = 1;
  repeated LuckyGiftUserMultiplierStat multiplier_distribution = 2;
  repeated LuckyGiftUserJackpotHit jackpot_hits = 3;
  int64 jackpot_hit_total = 4;
  int64 snapshot_at_ms = 5;
  int32 jackpot_page = 6;
  int32 jackpot_page_size = 7;
  string next_jackpot_cursor = 8;
  bool jackpot_has_more = 9;
}

// ListLuckyGiftDailyStats 面向 Databi 的按日聚合：只读 lucky_draw_pool_day_stats 读模型
// （北京日 +08:00 分桶，由后台游标增量刷新），不扫抽奖事实表，避免 BI 访问打热事实库。
// strategy_version 过滤用于按策略族拆分展示（如幸运礼物V3 tab 只看 dynamic_v3）。
message ListLuckyGiftDailyStatsRequest {
  RequestMeta meta = 1;
  // pool_id 为空表示全部奖池汇总。
  string pool_id = 2;
  // strategy_version 为空表示不按策略过滤；取值 fixed_v2 / dynamic_v3。
  string strategy_version = 3;
  // [start_ms, end_ms) UTC epoch ms；服务端按北京日换算成 stat_day 闭区间。
  int64 start_ms = 4;
  int64 end_ms = 5;
  // region_ids 为空表示全部区域；按房间可见区域 ID 过滤（0=GLOBAL 房间只在不过滤时计入）。
  repeated int64 region_ids = 6;
}

message LuckyGiftDailyStat {
  // stat_day 是北京日（+08:00）YYYY-MM-DD。
  string stat_day = 1;
  int64 draw_count = 2;
  int64 turnover_coin = 3;
  // payout_coin 为用户可见返奖（effective），与既有 DrawSummary 口径一致，含未发放/失败行。
  int64 payout_coin = 4;
  int64 base_reward_coin = 5;
}

message ListLuckyGiftDailyStatsResponse {
  repeated LuckyGiftDailyStat stats = 1;
}
