package hostorg

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/userclient"
	"hyapp-admin-server/internal/integration/walletclient"
	walletv1 "hyapp.local/api/proto/wallet/v1"
)

const (
	coinSellerStatusActive        = "active"
	coinSellerMerchantAssetType   = "COIN_SELLER_COIN"
	coinSellerStockTypePurchase   = "usdt_purchase"
	coinSellerStockTypeCompensate = "coin_compensation"
	paidCurrencyUSDT              = "USDT"
	sortByDiamond                 = "diamond"
	sortByMerchantBalance         = "merchant_balance"
	sortByTotalRechargeUSDT       = "total_recharge_usdt"
	bdLeaderPositionAliasMaxRunes = 64
)

type Service struct {
	userClient   userclient.Client
	walletClient walletclient.Client
	reader       *Reader
}

func NewService(userClient userclient.Client, walletClient walletclient.Client, reader *Reader) *Service {
	return &Service{userClient: userClient, walletClient: walletClient, reader: reader}
}

func (s *Service) ListBDLeaders(ctx context.Context, query listQuery) ([]*userclient.BDProfile, int64, error) {
	query = normalizeListQuery(query)
	return s.reader.ListBDProfiles(ctx, query, "bd_leader")
}

func (s *Service) ListBDs(ctx context.Context, query listQuery) ([]*userclient.BDProfile, int64, error) {
	query = normalizeListQuery(query)
	return s.reader.ListBDProfiles(ctx, query, "bd")
}

func (s *Service) ListAgencies(ctx context.Context, query listQuery) ([]*userclient.Agency, int64, error) {
	query = normalizeListQuery(query)
	return s.reader.ListAgencies(ctx, query)
}

func (s *Service) ListHosts(ctx context.Context, query listQuery) ([]*userclient.HostProfile, int64, error) {
	query = normalizeListQuery(query)
	return s.reader.ListHostProfiles(ctx, query)
}

func (s *Service) ListCoinSellers(ctx context.Context, query listQuery) ([]*CoinSellerListItem, int64, error) {
	query = normalizeListQuery(query)
	return s.reader.ListCoinSellers(ctx, query)
}

func (s *Service) GetCoinSellerSalaryRates(ctx context.Context, regionID int64) ([]CoinSellerSalaryRateTier, error) {
	return s.reader.ListCoinSellerSalaryRates(ctx, regionID)
}

func (s *Service) ReplaceCoinSellerSalaryRates(ctx context.Context, actorID int64, regionID int64, req replaceCoinSellerSalaryRatesRequest) ([]CoinSellerSalaryRateTier, error) {
	tiers := make([]CoinSellerSalaryRateTier, 0, len(req.Tiers))
	for index, item := range req.Tiers {
		minUSDMinor, err := salaryRateUSDMinor(item.MinUSDMinor, item.MinUSD)
		if err != nil {
			return nil, fmt.Errorf("min_usd is invalid")
		}
		maxUSDMinor, err := salaryRateUSDMinor(item.MaxUSDMinor, item.MaxUSD)
		if err != nil {
			return nil, fmt.Errorf("max_usd is invalid")
		}
		enabled := item.Status == "active"
		if item.Enabled != nil {
			enabled = *item.Enabled
		}
		status := strings.ToLower(strings.TrimSpace(item.Status))
		if status == "" {
			if enabled {
				status = "active"
			} else {
				status = "disabled"
			}
		}
		if status != "active" && status != "disabled" {
			return nil, fmt.Errorf("status is invalid")
		}
		if minUSDMinor <= 0 || maxUSDMinor < minUSDMinor {
			return nil, fmt.Errorf("usd range is invalid")
		}
		if item.CoinPerUSD <= 0 || item.CoinPerUSD%100 != 0 {
			return nil, fmt.Errorf("coin_per_usd must be a positive multiple of 100")
		}
		sortOrder := item.SortOrder
		if sortOrder == 0 {
			sortOrder = (index + 1) * 10
		}
		tiers = append(tiers, CoinSellerSalaryRateTier{
			RegionID:    regionID,
			MinUSDMinor: minUSDMinor,
			MaxUSDMinor: maxUSDMinor,
			MinUSD:      formatUSDMinor(minUSDMinor),
			MaxUSD:      formatUSDMinor(maxUSDMinor),
			CoinPerUSD:  item.CoinPerUSD,
			Status:      status,
			Enabled:     status == "active",
			SortOrder:   sortOrder,
		})
	}
	if err := validateCoinSellerSalaryRateTiers(tiers); err != nil {
		return nil, err
	}
	return s.reader.ReplaceCoinSellerSalaryRates(ctx, regionID, actorID, tiers)
}

func (s *Service) CreateBDLeader(ctx context.Context, actorID int64, requestID string, req createBDLeaderRequest) (*userclient.BDProfile, error) {
	targetUserID, err := s.resolveDisplayUserID(ctx, requestID, req.TargetUserID)
	if err != nil {
		return nil, err
	}
	positionAlias, err := normalizeBDLeaderPositionAlias(req.PositionAlias)
	if err != nil {
		return nil, err
	}
	// BD Leader 区域由目标用户当前 users.region_id 派生；后台不再传区域，避免创建角色时顺手迁移用户国家/区域。
	profile, err := s.userClient.CreateBDLeader(ctx, userclient.CreateBDLeaderRequest{
		RequestID:    requestID,
		Caller:       "hyapp-admin-server",
		CommandID:    strings.TrimSpace(req.CommandID),
		AdminUserID:  actorID,
		TargetUserID: targetUserID,
		Reason:       strings.TrimSpace(req.Reason),
	})
	if err != nil {
		return nil, err
	}
	// 职位别名只影响 Flutter H5 配置里 admin 入口的展示名，不参与 user-service 的身份创建事务，避免把后台展示字段扩散到 App 身份主协议。
	if err := s.reader.SaveBDLeaderPositionAlias(ctx, targetUserID, actorID, positionAlias); err != nil {
		return nil, err
	}
	profile.PositionAlias = positionAlias
	return profile, nil
}

func (s *Service) UpdateBDLeaderPositionAlias(ctx context.Context, actorID int64, targetUserID int64, req bdLeaderPositionAliasRequest) (*userclient.BDProfile, error) {
	if targetUserID <= 0 {
		return nil, fmt.Errorf("target_user_id is required")
	}
	positionAlias, err := normalizeBDLeaderPositionAlias(req.PositionAlias)
	if err != nil {
		return nil, err
	}
	profile, err := s.reader.GetBDLeader(ctx, targetUserID)
	if err != nil {
		return nil, err
	}
	// 行内编辑只修改后台维护的展示别名；先确认 BD Leader 身份存在，再写覆盖表，避免产生无法在列表里追踪的孤立别名。
	if err := s.reader.SaveBDLeaderPositionAlias(ctx, targetUserID, actorID, positionAlias); err != nil {
		return nil, err
	}
	profile.PositionAlias = positionAlias
	return profile, nil
}

func (s *Service) CreateBD(ctx context.Context, actorID int64, requestID string, req createBDRequest) (*userclient.BDProfile, error) {
	targetUserID, err := s.resolveDisplayUserID(ctx, requestID, req.TargetUserID)
	if err != nil {
		return nil, err
	}
	parentLeaderUserID := int64(0)
	if req.ParentLeaderUserID < 0 {
		return nil, fmt.Errorf("parent_leader_user_id must not be negative")
	}
	if req.ParentLeaderUserID > 0 {
		// 父级 Leader 是可选关系；传入短 ID 时才解析成内部 user_id，未传时保留 0 表示独立 BD。
		parentLeaderUserID, err = s.resolveDisplayUserID(ctx, requestID, req.ParentLeaderUserID)
		if err != nil {
			return nil, err
		}
	}
	return s.userClient.CreateBD(ctx, userclient.CreateBDRequest{
		RequestID:          requestID,
		Caller:             "hyapp-admin-server",
		CommandID:          strings.TrimSpace(req.CommandID),
		AdminUserID:        actorID,
		TargetUserID:       targetUserID,
		ParentLeaderUserID: parentLeaderUserID,
		Reason:             strings.TrimSpace(req.Reason),
	})
}

func (s *Service) SetBDStatus(ctx context.Context, actorID int64, targetUserID int64, requestID string, role string, req bdStatusRequest) (*userclient.BDProfile, error) {
	return s.userClient.SetBDStatus(ctx, userclient.SetBDStatusRequest{
		RequestID:    requestID,
		Caller:       "hyapp-admin-server",
		CommandID:    strings.TrimSpace(req.CommandID),
		AdminUserID:  actorID,
		TargetUserID: targetUserID,
		Role:         role,
		Status:       strings.TrimSpace(req.Status),
		Reason:       strings.TrimSpace(req.Reason),
	})
}

func (s *Service) DeleteBDLeader(ctx context.Context, targetUserID int64) (*userclient.BDProfile, error) {
	if targetUserID <= 0 {
		return nil, fmt.Errorf("target_user_id is required")
	}
	return s.reader.DeleteBDLeader(ctx, targetUserID)
}

func (s *Service) CreateCoinSeller(ctx context.Context, actorID int64, requestID string, req createCoinSellerRequest) (*userclient.CoinSellerProfile, error) {
	targetUserID, err := s.resolveDisplayUserID(ctx, requestID, req.TargetUserID)
	if err != nil {
		return nil, err
	}
	profile, err := s.userClient.CreateCoinSeller(ctx, userclient.CreateCoinSellerRequest{
		RequestID:    requestID,
		Caller:       "hyapp-admin-server",
		CommandID:    strings.TrimSpace(req.CommandID),
		AdminUserID:  actorID,
		TargetUserID: targetUserID,
		Reason:       strings.TrimSpace(req.Reason),
	})
	if err != nil {
		return nil, err
	}
	contact := firstNonBlank(req.Contact, req.Reason)
	if contact != "" {
		if err := s.reader.UpdateCoinSellerContact(ctx, targetUserID, contact); err != nil {
			return nil, err
		}
	}
	return profile, nil
}

func (s *Service) SetCoinSellerStatus(ctx context.Context, actorID int64, targetUserID int64, requestID string, req coinSellerStatusRequest) (*userclient.CoinSellerProfile, error) {
	profile, err := s.userClient.SetCoinSellerStatus(ctx, userclient.SetCoinSellerStatusRequest{
		RequestID:    requestID,
		Caller:       "hyapp-admin-server",
		CommandID:    strings.TrimSpace(req.CommandID),
		AdminUserID:  actorID,
		TargetUserID: targetUserID,
		Status:       strings.TrimSpace(req.Status),
		Reason:       strings.TrimSpace(req.Reason),
	})
	if err != nil {
		return nil, err
	}
	if req.Contact != nil {
		if err := s.reader.UpdateCoinSellerContact(ctx, targetUserID, *req.Contact); err != nil {
			return nil, err
		}
	}
	return profile, nil
}

func (s *Service) CreditCoinSellerStock(ctx context.Context, actorID int64, sellerUserID int64, requestID string, req coinSellerStockCreditRequest) (*walletv1.AdminCreditCoinSellerStockResponse, error) {
	if s.walletClient == nil {
		return nil, fmt.Errorf("wallet client is not configured")
	}
	if sellerUserID <= 0 || actorID <= 0 {
		return nil, fmt.Errorf("seller_user_id and operator_user_id are required")
	}
	commandID := strings.TrimSpace(req.CommandID)
	stockType := strings.ToLower(strings.TrimSpace(req.Type))
	evidenceRef := strings.TrimSpace(req.EvidenceRef)
	reason := strings.TrimSpace(req.Reason)
	if commandID == "" || req.CoinAmount <= 0 {
		return nil, fmt.Errorf("command_id and coin_amount are required")
	}

	paidCurrencyCode := ""
	paidAmountMicro := int64(0)
	paymentRef := strings.TrimSpace(req.PaymentRef)
	switch stockType {
	case coinSellerStockTypePurchase:
		paidCurrencyCode = paidCurrencyUSDT
		parsed, err := parseUSDTAmountMicro(req.RechargeAmount)
		if err != nil {
			return nil, err
		}
		paidAmountMicro = parsed
	case coinSellerStockTypeCompensate:
		if strings.TrimSpace(req.RechargeAmount) != "" || paymentRef != "" {
			return nil, fmt.Errorf("coin compensation must not include recharge_amount or payment_ref")
		}
	default:
		return nil, fmt.Errorf("stock type is invalid")
	}

	profile, err := s.userClient.GetCoinSellerProfile(ctx, userclient.GetCoinSellerProfileRequest{
		RequestID: requestID,
		Caller:    "hyapp-admin-server",
		UserID:    sellerUserID,
	})
	if err != nil {
		return nil, err
	}
	if profile == nil || profile.Status != coinSellerStatusActive || profile.MerchantAssetType != coinSellerMerchantAssetType {
		return nil, fmt.Errorf("target user is not an active coin seller")
	}
	seller, err := s.userClient.GetUser(ctx, userclient.GetUserRequest{
		RequestID: requestID,
		Caller:    "hyapp-admin-server",
		UserID:    sellerUserID,
	})
	if err != nil {
		return nil, err
	}
	if seller == nil || seller.CountryID <= 0 || seller.RegionID <= 0 {
		return nil, fmt.Errorf("coin seller country and region are required")
	}

	return s.walletClient.AdminCreditCoinSellerStock(ctx, &walletv1.AdminCreditCoinSellerStockRequest{
		CommandId:        commandID,
		SellerUserId:     sellerUserID,
		SellerCountryId:  seller.CountryID,
		SellerRegionId:   seller.RegionID,
		StockType:        stockType,
		CoinAmount:       req.CoinAmount,
		PaidCurrencyCode: paidCurrencyCode,
		PaidAmountMicro:  paidAmountMicro,
		PaymentRef:       paymentRef,
		EvidenceRef:      evidenceRef,
		OperatorUserId:   actorID,
		Reason:           reason,
		AppCode:          appctx.FromContext(ctx),
	})
}

func (s *Service) CreateAgency(ctx context.Context, actorID int64, requestID string, req createAgencyRequest) (*userclient.CreateAgencyResult, error) {
	ownerUserID, err := s.resolveDisplayUserID(ctx, requestID, req.OwnerUserID)
	if err != nil {
		return nil, err
	}
	parentBDUserID := int64(0)
	if req.ParentBDUserID < 0 {
		return nil, fmt.Errorf("parent_bd_user_id must not be negative")
	}
	if req.ParentBDUserID > 0 {
		// 父级 BD 是可选关系；传入短 ID 时才解析成内部 user_id，未传时保留 0 表示独立 Agency。
		parentBDUserID, err = s.resolveDisplayUserID(ctx, requestID, req.ParentBDUserID)
		if err != nil {
			return nil, err
		}
	}
	joinEnabled := false
	if req.JoinEnabled != nil {
		joinEnabled = *req.JoinEnabled
	}
	return s.userClient.CreateAgency(ctx, userclient.CreateAgencyRequest{
		RequestID:      requestID,
		Caller:         "hyapp-admin-server",
		CommandID:      strings.TrimSpace(req.CommandID),
		AdminUserID:    actorID,
		OwnerUserID:    ownerUserID,
		ParentBDUserID: parentBDUserID,
		Name:           strings.TrimSpace(req.Name),
		JoinEnabled:    joinEnabled,
		Reason:         strings.TrimSpace(req.Reason),
	})
}

func (s *Service) CloseAgency(ctx context.Context, actorID int64, agencyID int64, requestID string, req agencyCloseRequest) (*userclient.Agency, error) {
	return s.userClient.CloseAgency(ctx, userclient.CloseAgencyRequest{
		RequestID:   requestID,
		Caller:      "hyapp-admin-server",
		CommandID:   strings.TrimSpace(req.CommandID),
		AdminUserID: actorID,
		AgencyID:    agencyID,
		Reason:      strings.TrimSpace(req.Reason),
	})
}

func (s *Service) DeleteAgency(ctx context.Context, actorID int64, agencyID int64, requestID string, req agencyDeleteRequest) (*userclient.Agency, error) {
	return s.userClient.DeleteAgency(ctx, userclient.DeleteAgencyRequest{
		RequestID:   requestID,
		Caller:      "hyapp-admin-server",
		CommandID:   strings.TrimSpace(req.CommandID),
		AdminUserID: actorID,
		AgencyID:    agencyID,
		Reason:      strings.TrimSpace(req.Reason),
	})
}

func (s *Service) resolveDisplayUserID(ctx context.Context, requestID string, displayUserID int64) (int64, error) {
	if displayUserID <= 0 {
		return 0, fmt.Errorf("display_user_id is required")
	}
	identity, err := s.userClient.ResolveDisplayUserID(ctx, userclient.ResolveDisplayUserIDRequest{
		RequestID:     requestID,
		Caller:        "hyapp-admin-server",
		DisplayUserID: strconv.FormatInt(displayUserID, 10),
	})
	if err != nil {
		return 0, err
	}
	if identity == nil || identity.UserID <= 0 {
		return 0, fmt.Errorf("display_user_id %d not found", displayUserID)
	}
	return identity.UserID, nil
}

func normalizeBDLeaderPositionAlias(value string) (string, error) {
	positionAlias := strings.TrimSpace(value)
	if len([]rune(positionAlias)) > bdLeaderPositionAliasMaxRunes {
		return "", fmt.Errorf("position_alias must not exceed %d characters", bdLeaderPositionAliasMaxRunes)
	}
	return positionAlias, nil
}

func (s *Service) SetAgencyJoinEnabled(ctx context.Context, actorID int64, agencyID int64, requestID string, req agencyJoinEnabledRequest) (*userclient.Agency, error) {
	joinEnabled := false
	if req.JoinEnabled != nil {
		joinEnabled = *req.JoinEnabled
	}
	return s.userClient.SetAgencyJoinEnabled(ctx, userclient.SetAgencyJoinEnabledRequest{
		RequestID:   requestID,
		Caller:      "hyapp-admin-server",
		CommandID:   strings.TrimSpace(req.CommandID),
		AdminUserID: actorID,
		AgencyID:    agencyID,
		JoinEnabled: joinEnabled,
		Reason:      strings.TrimSpace(req.Reason),
	})
}

func parseUSDTAmountMicro(raw string) (int64, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return 0, fmt.Errorf("recharge_amount is required for usdt purchase")
	}
	parts := strings.Split(raw, ".")
	if len(parts) > 2 || parts[0] == "" {
		return 0, fmt.Errorf("recharge_amount is invalid")
	}
	whole := parts[0]
	frac := ""
	if len(parts) == 2 {
		frac = parts[1]
		if frac == "" || len(frac) > 6 {
			return 0, fmt.Errorf("recharge_amount supports up to 6 decimals")
		}
	}
	digits := whole + frac + strings.Repeat("0", 6-len(frac))
	var amount int64
	const maxInt64 = int64(^uint64(0) >> 1)
	for _, char := range digits {
		if char < '0' || char > '9' {
			return 0, fmt.Errorf("recharge_amount is invalid")
		}
		next := int64(char - '0')
		if amount > (maxInt64-next)/10 {
			return 0, fmt.Errorf("recharge_amount is too large")
		}
		amount = amount*10 + next
	}
	if amount <= 0 {
		return 0, fmt.Errorf("recharge_amount must be positive")
	}
	return amount, nil
}

func salaryRateUSDMinor(minor int64, raw string) (int64, error) {
	if minor > 0 {
		return minor, nil
	}
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return 0, fmt.Errorf("usd amount is required")
	}
	parts := strings.Split(raw, ".")
	if len(parts) > 2 || parts[0] == "" {
		return 0, fmt.Errorf("usd amount is invalid")
	}
	digits := parts[0]
	fraction := ""
	if len(parts) == 2 {
		fraction = parts[1]
		if len(fraction) > 2 {
			return 0, fmt.Errorf("usd amount supports up to 2 decimals")
		}
	}
	digits += fraction + strings.Repeat("0", 2-len(fraction))
	var amount int64
	const maxInt64 = int64(^uint64(0) >> 1)
	for _, char := range digits {
		if char < '0' || char > '9' {
			return 0, fmt.Errorf("usd amount is invalid")
		}
		next := int64(char - '0')
		if amount > (maxInt64-next)/10 {
			return 0, fmt.Errorf("usd amount is too large")
		}
		amount = amount*10 + next
	}
	return amount, nil
}

func validateCoinSellerSalaryRateTiers(tiers []CoinSellerSalaryRateTier) error {
	active := make([]CoinSellerSalaryRateTier, 0, len(tiers))
	for _, tier := range tiers {
		if tier.Status == "active" {
			active = append(active, tier)
		}
	}
	for i := 0; i < len(active); i++ {
		for j := i + 1; j < len(active); j++ {
			if active[i].MinUSDMinor <= active[j].MaxUSDMinor && active[j].MinUSDMinor <= active[i].MaxUSDMinor {
				return fmt.Errorf("active usd ranges must not overlap")
			}
		}
	}
	return nil
}

func formatUSDMinor(value int64) string {
	sign := ""
	if value < 0 {
		sign = "-"
		value = -value
	}
	return fmt.Sprintf("%s%d.%02d", sign, value/100, value%100)
}

func normalizeListQuery(query listQuery) listQuery {
	if query.Page < 1 {
		query.Page = 1
	}
	if query.PageSize < 1 {
		query.PageSize = 20
	}
	if query.PageSize > 100 {
		query.PageSize = 100
	}
	query.Keyword = strings.TrimSpace(query.Keyword)
	query.Status = strings.ToLower(strings.TrimSpace(query.Status))
	query.SortBy = strings.ToLower(strings.TrimSpace(query.SortBy))
	switch query.SortBy {
	case sortByDiamond, sortByMerchantBalance, sortByTotalRechargeUSDT:
	default:
		query.SortBy = ""
		query.SortDirection = ""
		return query
	}
	query.SortDirection = strings.ToLower(strings.TrimSpace(query.SortDirection))
	if query.SortDirection != "asc" {
		query.SortDirection = "desc"
	}
	return query
}

func firstNonBlank(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}
