package service

import (
	"context"
	"fmt"
	"strings"
	"time"

	roomv1 "hyapp.local/api/proto/room/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/room-service/internal/integration"
)

const (
	// tieredPrivilegeVIPProgram 是 P1 权益制 VIP 的稳定策略名。App 选择哪套体系由 wallet 配置决定，
	// room-service 禁止再按 fami/lalu 写死分支，否则后续 App 无法复用同一房间权限实现。
	tieredPrivilegeVIPProgram = "tiered_privilege_v1"

	vipBenefitCustomRoomBackground = "custom_room_background"
	vipBenefitRoomImageMessage     = "room_image_message"
	vipBenefitAnimatedRoomCover    = "animated_room_cover"
	vipBenefitRoomBorder           = "room_border"
	vipBenefitColoredRoomName      = "colored_room_name"
	vipBenefitAntiKick             = "anti_kick"
	vipBenefitAntiMute             = "anti_mute"
	vipBenefitRoomEntryNotice      = "room_entry_notice"

	// VIP 查询会进入进房展示或低频房管动作，必须给同步依赖设置短上限，避免 wallet 抖动长期占住房间请求。
	roomVIPLookupTimeout = time.Second
)

// effectiveVIPAccess 是 room-service 从 wallet effective state 裁剪出的房间权限快照。
// paid/trial 合并、等级继承和体验卡过滤都已经由 wallet 完成，这里只做精确 benefit_code membership 判断。
type effectiveVIPAccess struct {
	programType                   string
	level                         int32
	name                          string
	expiresAtMS                   int64
	fallbackLevel                 int32
	fallbackExpiresAtMS           int64
	benefits                      map[string]struct{}
	roomEntryNoticeSettingEnabled bool
}

func (access effectiveVIPAccess) hasBenefit(code string) bool {
	_, exists := access.benefits[normalizeVIPCode(code)]
	return exists
}

func (access effectiveVIPAccess) roomEntryNoticeEnabled() bool {
	return access.roomEntryNoticeSettingEnabled && access.hasBenefit(vipBenefitRoomEntryNotice)
}

func (access effectiveVIPAccess) roomSnapshot() *roomv1.RoomEffectiveVipSnapshot {
	return &roomv1.RoomEffectiveVipSnapshot{
		ProgramType:            access.programType,
		Level:                  access.level,
		Name:                   access.name,
		RoomEntryNoticeEnabled: access.roomEntryNoticeEnabled(),
	}
}

// loadEffectiveVIPAccess 调 wallet GetMyVip 一次并只接受完整 effective state。
// 空 VIP 是合法结果，但 program_config 不能为空；缺配置时无法判断当前 App 是否应执行 P1 权限，必须按上游失败处理。
func (s *Service) loadEffectiveVIPAccess(ctx context.Context, requestID string, userID int64) (effectiveVIPAccess, error) {
	if s == nil || s.wallet == nil {
		return effectiveVIPAccess{}, xerr.New(xerr.Unavailable, "wallet vip client is unavailable")
	}
	if userID <= 0 {
		return effectiveVIPAccess{}, xerr.New(xerr.InvalidArgument, "vip user_id is required")
	}

	lookupCtx, cancel := context.WithTimeout(ctx, roomVIPLookupTimeout)
	defer cancel()
	response, err := s.wallet.GetMyVip(lookupCtx, &walletv1.GetMyVipRequest{
		RequestId: strings.TrimSpace(requestID),
		AppCode:   appcode.FromContext(ctx),
		UserId:    userID,
	})
	if err != nil {
		return effectiveVIPAccess{}, xerr.New(xerr.Unavailable, fmt.Sprintf("get effective vip failed: %v", err))
	}
	if response == nil || response.GetState() == nil || response.GetState().GetProgramConfig() == nil {
		return effectiveVIPAccess{}, xerr.New(xerr.Unavailable, "wallet vip state is incomplete")
	}

	state := response.GetState()
	access := effectiveVIPAccess{
		programType:                   normalizeVIPCode(state.GetProgramConfig().GetProgramType()),
		benefits:                      make(map[string]struct{}, len(state.GetEffectiveBenefits())),
		roomEntryNoticeSettingEnabled: true,
	}
	// 旧 wallet 在滚动升级期间没有 user_settings tag；nil 必须兼容为产品默认开启。
	if settings := state.GetUserSettings(); settings != nil {
		access.roomEntryNoticeSettingEnabled = settings.GetRoomEntryNoticeEnabled()
	}
	if access.programType == "" {
		return effectiveVIPAccess{}, xerr.New(xerr.Unavailable, "wallet vip program is missing")
	}
	if effective := state.GetEffectiveVip(); effective != nil && effective.GetActive() {
		access.level = effective.GetLevel()
		access.name = strings.TrimSpace(effective.GetName())
		access.expiresAtMS = effective.GetExpiresAtMs()
	}
	if normalizeVIPCode(state.GetEffectiveSource()) == ledgerVipEffectiveSourceTrial {
		if paid := state.GetPaidVip(); paid != nil && paid.GetActive() && paid.GetLevel() > 0 {
			// 体验卡自然到期时 wallet 不会产生一次写事务事件；把 wallet 已求值的 paid
			// 回落计划一起投影，列表可在毫秒边界切换而无需同步回查或复制 trial 合并规则。
			access.fallbackLevel = paid.GetLevel()
			access.fallbackExpiresAtMS = paid.GetExpiresAtMs()
		}
	}
	for _, benefit := range state.GetEffectiveBenefits() {
		if benefit == nil {
			continue
		}
		if code := normalizeVIPCode(benefit.GetBenefitCode()); code != "" {
			access.benefits[code] = struct{}{}
		}
	}
	return access, nil
}

const ledgerVipEffectiveSourceTrial = "trial"

// requireCustomRoomBackgroundBenefit 只对配置为 P1 权益制的 App 执行自定义背景门禁。
// action 由调用点传入精确动作，Flutter 才能区分上传、保存和切换失败并保留对应本地状态。
// legacy_timed 保持 Lalu 原行为；新 App 只需在 wallet 切换 program_type，无需再修改 room-service。
func (s *Service) requireCustomRoomBackgroundBenefit(ctx context.Context, requestID string, ownerUserID int64, action string) error {
	return s.requireVIPBenefit(ctx, requestID, ownerUserID, vipBenefitCustomRoomBackground, action)
}

// requireVIPBenefit 优先调用 wallet 的单项权益判定，并把升级页需要的等级事实放进错误 metadata。
// legacy_timed App 沿用旧行为；P1 App 在 wallet 不可用或没有权益时都失败关闭，避免故障窗口越权。
func (s *Service) requireVIPBenefit(ctx context.Context, requestID string, userID int64, benefitCode string, action string) error {
	if checker, ok := s.wallet.(integration.WalletVIPBenefitClient); ok {
		lookupCtx, cancel := context.WithTimeout(ctx, roomVIPLookupTimeout)
		defer cancel()
		response, err := checker.CheckVipBenefit(lookupCtx, &walletv1.CheckVipBenefitRequest{
			RequestId:   strings.TrimSpace(requestID),
			AppCode:     appcode.FromContext(ctx),
			UserId:      userID,
			BenefitCode: normalizeVIPCode(benefitCode),
		})
		if err != nil {
			// 对外错误只暴露稳定的业务语义，底层 gRPC 状态和地址不能进入 HTTP envelope。
			return xerr.New(xerr.Unavailable, "check vip benefit failed")
		}
		if response == nil || response.GetState() == nil || response.GetState().GetProgramConfig() == nil {
			return xerr.New(xerr.Unavailable, "wallet vip benefit state is incomplete")
		}
		if normalizeVIPCode(response.GetState().GetProgramConfig().GetProgramType()) != tieredPrivilegeVIPProgram {
			return nil
		}
		if response.GetAllowed() {
			return nil
		}
		return xerr.NewWithMetadata(xerr.VIPBenefitRequired, "vip benefit is required", map[string]string{
			"benefit_code":            normalizeVIPCode(benefitCode),
			"required_level":          fmt.Sprintf("%d", response.GetRequiredLevel()),
			"current_effective_level": fmt.Sprintf("%d", response.GetCurrentEffectiveLevel()),
			"action":                  strings.TrimSpace(action),
		})
	}

	// 兼容滚动升级和现有测试 fake：旧 client 仍可从 effective_benefits 得到同一授权事实。
	access, err := s.loadEffectiveVIPAccess(ctx, requestID, userID)
	if err != nil {
		// 权限事实不可用时必须失败关闭；放行会让无权益用户在 wallet 故障窗口获得可持久化背景权限。
		return err
	}
	if access.programType != tieredPrivilegeVIPProgram {
		return nil
	}
	if !access.hasBenefit(benefitCode) {
		return xerr.NewWithMetadata(xerr.VIPBenefitRequired, "vip benefit is required", map[string]string{
			"benefit_code":            normalizeVIPCode(benefitCode),
			"required_level":          "0",
			"current_effective_level": fmt.Sprintf("%d", access.level),
			"action":                  strings.TrimSpace(action),
		})
	}
	return nil
}

// requireTargetWithoutVIPBenefit 保护普通房主/管理员治理入口；平台治理必须继续走 SystemEvictUser，不能复用该守卫。
func (s *Service) requireTargetWithoutVIPBenefit(ctx context.Context, requestID string, targetUserID int64, benefitCode string) error {
	access, err := s.loadEffectiveVIPAccess(ctx, requestID, targetUserID)
	if err != nil {
		// anti_kick/anti_mute 属于付费保护，wallet 不可判定时宁可拒绝普通房管动作，也不能错误剥夺已生效权益。
		return err
	}
	if access.hasBenefit(benefitCode) {
		switch normalizeVIPCode(benefitCode) {
		case vipBenefitAntiKick:
			return xerr.New(xerr.VIPAntiKick, "target vip benefit prevents kick")
		case vipBenefitAntiMute:
			return xerr.New(xerr.VIPAntiMute, "target vip benefit prevents mute")
		default:
			return xerr.New(xerr.VIPBenefitRequired, "target vip benefit prevents this room moderation action")
		}
	}
	return nil
}

func normalizeVIPCode(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}
