package agent_open_api

import (
	"context"
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"time"

	cardObservationApp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
	domainwallet "github.com/break/junhong_cmp_fiber/internal/domain/wallet"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	assetSvc "github.com/break/junhong_cmp_fiber/internal/service/asset"
	deviceSvc "github.com/break/junhong_cmp_fiber/internal/service/device"
	iotCardSvc "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
	orderSvc "github.com/break/junhong_cmp_fiber/internal/service/order"
	packageSvc "github.com/break/junhong_cmp_fiber/internal/service/package"
	shopCommissionSvc "github.com/break/junhong_cmp_fiber/internal/service/shop_commission"
	"github.com/break/junhong_cmp_fiber/internal/store/postgres"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/middleware"
	"gorm.io/gorm"
)

// Service 代理开放接口业务编排服务
type Service struct {
	assetService          *assetSvc.Service
	packageService        *packageSvc.Service
	orderService          *orderSvc.Service
	shopCommissionService *shopCommissionSvc.Service
	stopResumeService     *iotCardSvc.StopResumeService
	deviceService         *deviceSvc.Service
	iotCardStore          *postgres.IotCardStore
	packageUsageStore     *postgres.PackageUsageStore
	packageStore          *postgres.PackageStore
	packageSeriesStore    *postgres.PackageSeriesStore
	agentWalletStore      *postgres.AgentWalletStore
	deviceSimBindingStore *postgres.DeviceSimBindingStore
	deviceStore           *postgres.DeviceStore
	observationSeries     cardObservationApp.BestEffortSeriesDispatcher
}

// SetObservationSeriesDispatcher 注入 OpenAPI 读取后的后台观测序列端口。
func (s *Service) SetObservationSeriesDispatcher(dispatcher cardObservationApp.BestEffortSeriesDispatcher) {
	s.observationSeries = dispatcher
}

// New 创建代理开放接口业务编排服务
func New(
	assetService *assetSvc.Service,
	packageService *packageSvc.Service,
	orderService *orderSvc.Service,
	shopCommissionService *shopCommissionSvc.Service,
	stopResumeService *iotCardSvc.StopResumeService,
	deviceService *deviceSvc.Service,
	iotCardStore *postgres.IotCardStore,
	packageUsageStore *postgres.PackageUsageStore,
	packageStore *postgres.PackageStore,
	packageSeriesStore *postgres.PackageSeriesStore,
	agentWalletStore *postgres.AgentWalletStore,
	deviceSimBindingStore *postgres.DeviceSimBindingStore,
	deviceStore *postgres.DeviceStore,
) *Service {
	return &Service{
		assetService:          assetService,
		packageService:        packageService,
		orderService:          orderService,
		shopCommissionService: shopCommissionService,
		stopResumeService:     stopResumeService,
		deviceService:         deviceService,
		iotCardStore:          iotCardStore,
		packageUsageStore:     packageUsageStore,
		packageStore:          packageStore,
		packageSeriesStore:    packageSeriesStore,
		agentWalletStore:      agentWalletStore,
		deviceSimBindingStore: deviceSimBindingStore,
		deviceStore:           deviceStore,
	}
}

// GetCardTraffic 查询开放接口流量
// 支持三种解析路径：
// 1. card_no 解析为独立卡 → 查卡维度流量
// 2. card_no 解析为已绑定设备的卡 → 反查绑定设备 → 查设备维度流量
// 3. card_no 无法解析为卡 → 尝试作为设备标识（IMEI/虚拟号）→ 查设备维度流量
func (s *Service) GetCardTraffic(ctx context.Context, req *dto.AgentOpenAPICardQueryRequest) (*dto.AgentOpenAPICardTrafficResponse, error) {
	card, cardErr := s.resolveOpenAPICard(ctx, req.CardNo)
	if cardErr == nil {
		// 独立卡：直接查卡维度流量
		if card.IsStandalone {
			resp, err := s.buildCardTrafficResponse(ctx, req.CardNo, "iot_card", card.ID, "")
			if err == nil {
				s.dispatchCardObservation(ctx, constants.CardObservationSceneOpenCardTraffic, card.ID, constants.CardObservationSyncTypeTraffic)
			}
			return resp, err
		}
		// 已绑定设备的卡：反查设备后查设备维度流量
		binding, err := s.deviceSimBindingStore.GetActiveBindingByCardID(ctx, card.ID)
		if err != nil {
			return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡绑定设备失败")
		}
		device, err := s.deviceStore.GetByID(ctx, binding.DeviceID)
		if err != nil {
			return nil, errors.Wrap(errors.CodeInternalError, err, "查询绑定设备信息失败")
		}
		resp, buildErr := s.buildCardTrafficResponse(ctx, req.CardNo, "device", device.ID, device.VirtualNo)
		if buildErr == nil {
			s.dispatchCardObservation(ctx, constants.CardObservationSceneOpenCardTraffic, card.ID, constants.CardObservationSyncTypeTraffic)
		}
		return resp, buildErr
	}

	// 兜底：尝试解析为设备标识（支持 IMEI/虚拟号）
	device, err := s.resolveOpenAPIDevice(ctx, req.CardNo)
	if err != nil {
		// 两种解析都失败，返回原始卡解析错误（语义更贴近入参）
		return nil, cardErr
	}
	resp, buildErr := s.buildCardTrafficResponse(ctx, req.CardNo, "device", device.ID, device.VirtualNo)
	if buildErr == nil {
		s.dispatchDeviceCardObservations(ctx, constants.CardObservationSceneOpenCardTraffic, device.ID, constants.CardObservationSyncTypeTraffic)
	}
	return resp, buildErr
}

// buildCardTrafficResponse 统一构造卡流量响应，支持卡和设备两种载体维度
func (s *Service) buildCardTrafficResponse(ctx context.Context, cardNo, carrierType string, carrierID uint, deviceNo string) (*dto.AgentOpenAPICardTrafficResponse, error) {
	activeStatus := constants.PackageUsageStatusActive
	depletedStatus := constants.PackageUsageStatusDepleted
	pendingStatus := constants.PackageUsageStatusPending
	activeUsages, err := s.packageUsageStore.ListByCarrier(ctx, carrierType, carrierID, &activeStatus)
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询生效套餐失败")
	}
	// 已用完的套餐仍在有效期内，与生效中套餐一并展示
	depletedUsages, err := s.packageUsageStore.ListByCarrier(ctx, carrierType, carrierID, &depletedStatus)
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询已用完套餐失败")
	}
	activeUsages = append(activeUsages, depletedUsages...)
	pendingUsages, err := s.packageUsageStore.ListByCarrier(ctx, carrierType, carrierID, &pendingStatus)
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询待生效套餐失败")
	}

	packageMap, seriesMap, err := s.loadUsagePackageContext(ctx, append(activeUsages, pendingUsages...))
	if err != nil {
		return nil, err
	}

	resp := &dto.AgentOpenAPICardTrafficResponse{
		CardNo:          cardNo,
		DeviceNo:        deviceNo,
		ActivePackages:  make([]dto.AgentOpenAPIPackageTrafficItem, 0, len(activeUsages)),
		PendingPackages: make([]dto.AgentOpenAPIPackageTrafficItem, 0, len(pendingUsages)),
	}
	for _, usage := range activeUsages {
		item := s.buildTrafficItem(usage, packageMap, seriesMap, true)
		resp.ActivePackages = append(resp.ActivePackages, item)
		resp.ActiveTotalFlowMB += item.TotalFlowMB
		resp.ActiveUsedFlowMB += item.UsedFlowMB
		resp.ActiveRemainingFlowMB += item.RemainingFlowMB
		if item.ExpiresAt != nil && (resp.ActiveExpiresAt == nil || item.ExpiresAt.After(*resp.ActiveExpiresAt)) {
			resp.ActiveExpiresAt = item.ExpiresAt
		}
	}
	for _, usage := range pendingUsages {
		resp.PendingPackages = append(resp.PendingPackages, s.buildTrafficItem(usage, packageMap, seriesMap, false))
	}
	return resp, nil
}

// GetCardStatus 查询开放接口单卡状态
func (s *Service) GetCardStatus(ctx context.Context, req *dto.AgentOpenAPICardQueryRequest) (*dto.AgentOpenAPICardStatusResponse, error) {
	card, err := s.resolveOpenAPICard(ctx, req.CardNo)
	if err != nil {
		return nil, err
	}

	cardStatus := constants.AgentOpenAPICardStatusStopped
	stopReason := card.StopReason
	if card.NetworkStatus == constants.NetworkStatusOnline {
		cardStatus = constants.AgentOpenAPICardStatusNormal
		stopReason = ""
	}

	resp := &dto.AgentOpenAPICardStatusResponse{
		CardNo:         req.CardNo,
		CardStatus:     cardStatus,
		CardStatusName: constants.AgentOpenAPICardStatusName(cardStatus),
		GatewayExtend:  card.GatewayExtend,
		StopReason:     stopReason,
		StopReasonName: constants.AgentOpenAPIStopReasonName(stopReason),
	}
	s.dispatchCardObservation(ctx, constants.CardObservationSceneOpenCardNetwork, card.ID, constants.CardObservationSyncTypeNetwork)
	return resp, nil
}

// GetRealnameStatus 查询开放接口单卡实名状态
func (s *Service) GetRealnameStatus(ctx context.Context, req *dto.AgentOpenAPICardQueryRequest) (*dto.AgentOpenAPIRealnameStatusResponse, error) {
	card, err := s.resolveOpenAPICard(ctx, req.CardNo)
	if err != nil {
		return nil, err
	}

	resp := &dto.AgentOpenAPIRealnameStatusResponse{
		CardNo:      req.CardNo,
		IsRealnamed: card.RealNameStatus == constants.RealNameStatusVerified,
	}
	s.dispatchCardObservation(ctx, constants.CardObservationSceneOpenCardRealname, card.ID, constants.CardObservationSyncTypeRealname)
	return resp, nil
}

// ResumeCard 调用上游复机机卡分离停机卡
func (s *Service) ResumeCard(ctx context.Context, req *dto.AgentOpenAPICardResumeRequest) (*dto.AgentOpenAPICardResumeResponse, error) {
	card, err := s.resolveOpenAPICard(ctx, req.CardNo)
	if err != nil {
		return nil, err
	}
	if s.stopResumeService == nil {
		return nil, errors.New(errors.CodeInternalError, "开放接口复机依赖未配置")
	}
	if err := s.stopResumeService.StartMachineSeparatedCard(ctx, card); err != nil {
		return nil, err
	}

	return &dto.AgentOpenAPICardResumeResponse{
		CardNo:         req.CardNo,
		CardStatus:     constants.AgentOpenAPICardStatusNormal,
		CardStatusName: constants.AgentOpenAPICardStatusName(constants.AgentOpenAPICardStatusNormal),
		GatewayExtend:  "",
	}, nil
}

// ListPackages 查询开放接口代理可购买套餐列表
func (s *Service) ListPackages(ctx context.Context, req *dto.AgentOpenAPIPackageListRequest) ([]*dto.AgentOpenAPIPackageItem, int64, int, int, error) {
	status := constants.StatusEnabled
	shelfStatus := constants.ShelfStatusOn
	packageReq := &dto.PackageListRequest{
		Page:        req.Page,
		PageSize:    req.PageSize,
		PackageName: req.PackageName,
		SeriesID:    req.SeriesID,
		Status:      &status,
		ShelfStatus: &shelfStatus,
		PackageType: req.PackageType,
	}
	packages, total, err := s.packageService.List(ctx, packageReq)
	if err != nil {
		return nil, 0, 0, 0, err
	}

	items := make([]*dto.AgentOpenAPIPackageItem, 0, len(packages))
	for _, pkg := range packages {
		items = append(items, &dto.AgentOpenAPIPackageItem{
			PackageID:       pkg.ID,
			PackageCode:     pkg.PackageCode,
			PackageName:     pkg.PackageName,
			SeriesName:      stringValue(pkg.SeriesName),
			PackageType:     pkg.PackageType,
			PackageTypeName: constants.AgentOpenAPIPackageTypeName(pkg.PackageType),
			CostPrice:       pkg.CostPrice,
			RetailPrice:     int64Value(pkg.EffectiveRetailPrice),
			TotalFlowMB:     pkg.RealDataMB,
			ValidDays:       packageValidDays(pkg.DurationDays, pkg.DurationMonths),
		})
	}

	page, pageSize := normalizePage(req.Page, req.PageSize)
	return items, total, page, pageSize, nil
}

// GetWalletBalance 查询开放接口预充值主钱包余额
func (s *Service) GetWalletBalance(ctx context.Context) (*dto.AgentOpenAPIWalletBalanceResponse, error) {
	shopID := middleware.GetShopIDFromContext(ctx)
	if shopID == 0 {
		return nil, errors.New(errors.CodeUnauthorized)
	}

	wallet, err := s.agentWalletStore.GetMainWallet(ctx, shopID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return &dto.AgentOpenAPIWalletBalanceResponse{
				Balance:          0,
				FrozenBalance:    0,
				AvailableBalance: 0,
				Currency:         constants.AgentOpenAPIWalletCurrencyCNY,
			}, nil
		}
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询主钱包余额失败")
	}
	aggregate := domainwallet.AgentWallet{
		ID: wallet.ID, ShopID: wallet.ShopID, WalletType: wallet.WalletType,
		Balance: wallet.Balance, FrozenBalance: wallet.FrozenBalance,
		CreditEnabled: wallet.CreditEnabled, CreditLimit: wallet.CreditLimit,
		Status: wallet.Status, Version: wallet.Version,
	}
	if err := aggregate.Validate(); err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "代理主钱包资金状态异常")
	}
	cashAvailable, err := aggregate.CashAvailableBalance()
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "计算主钱包现金可用金额失败")
	}
	available, err := aggregate.AvailableBalance()
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "计算主钱包总可用金额失败")
	}
	debtAmount, err := aggregate.DebtAmount()
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "计算主钱包欠款金额失败")
	}

	return &dto.AgentOpenAPIWalletBalanceResponse{
		Balance:              wallet.Balance,
		FrozenBalance:        wallet.FrozenBalance,
		CashAvailableBalance: cashAvailable,
		CreditEnabled:        wallet.CreditEnabled,
		CreditLimit:          wallet.CreditLimit,
		AvailableBalance:     available,
		IsInDebt:             aggregate.IsInDebt(),
		DebtAmount:           debtAmount,
		Version:              wallet.Version,
		Currency:             walletCurrency(wallet.Currency),
	}, nil
}

// ListWalletTransactions 查询开放接口预充值主钱包流水
func (s *Service) ListWalletTransactions(ctx context.Context, req *dto.AgentOpenAPIWalletTransactionListRequest) (*dto.MainWalletTransactionListResponse, error) {
	shopID := middleware.GetShopIDFromContext(ctx)
	if shopID == 0 {
		return nil, errors.New(errors.CodeUnauthorized)
	}

	return s.shopCommissionService.ListMainWalletTransactions(ctx, shopID, &dto.MainWalletTransactionListRequest{
		Page:            req.Page,
		PageSize:        req.PageSize,
		TransactionType: req.TransactionType,
		StartDate:       req.StartDate,
		EndDate:         req.EndDate,
	})
}

// CreateWalletPackageOrders 使用代理预充值钱包按卡购买套餐
func (s *Service) CreateWalletPackageOrders(ctx context.Context, req *dto.AgentOpenAPIWalletPackageOrderRequest) (*dto.AgentOpenAPIWalletPackageOrderResponse, error) {
	shopID := middleware.GetShopIDFromContext(ctx)
	if shopID == 0 {
		return nil, errors.New(errors.CodeUnauthorized)
	}

	pkg, err := s.packageStore.GetByCode(ctx, strings.TrimSpace(req.PackageCode))
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeNotFound, "套餐不存在")
		}
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询套餐失败")
	}

	resp := &dto.AgentOpenAPIWalletPackageOrderResponse{
		BatchNo:     generateAgentOpenAPIBatchNo(),
		PackageCode: pkg.PackageCode,
		Orders:      []dto.AgentOpenAPIWalletPackageOrderItem{},
		FailedCards: []dto.AgentOpenAPIWalletPackageOrderFailure{},
	}

	for _, rawCardNo := range req.CardNos {
		cardNo := strings.TrimSpace(rawCardNo)
		if cardNo == "" {
			resp.FailedCards = append(resp.FailedCards, buildFailedCard(rawCardNo, errors.New(errors.CodeInvalidParam, "卡标识不能为空")))
			continue
		}
		var identifier string
		card, cardErr := s.resolveOpenAPICard(ctx, cardNo)
		if cardErr != nil {
			// 兜底：尝试解析为设备标识（支持 IMEI/虚拟号）
			device, devErr := s.resolveOpenAPIDevice(ctx, cardNo)
			if devErr != nil {
				resp.FailedCards = append(resp.FailedCards, buildFailedCard(cardNo, cardErr))
				continue
			}
			identifier = device.VirtualNo
		} else if !card.IsStandalone {
			// 卡已绑定设备时，自动转为设备维度购买
			binding, bindErr := s.deviceSimBindingStore.GetActiveBindingByCardID(ctx, card.ID)
			if bindErr != nil {
				resp.FailedCards = append(resp.FailedCards, buildFailedCard(cardNo, errors.Wrap(errors.CodeInternalError, bindErr, "查询卡绑定设备失败")))
				continue
			}
			device, devErr := s.deviceStore.GetByID(ctx, binding.DeviceID)
			if devErr != nil {
				resp.FailedCards = append(resp.FailedCards, buildFailedCard(cardNo, errors.Wrap(errors.CodeInternalError, devErr, "查询绑定设备信息失败")))
				continue
			}
			identifier = device.VirtualNo
		} else {
			identifier = card.ICCID
		}

		orderResp, err := s.orderService.CreateAdminOrder(ctx, &dto.CreateAdminOrderRequest{
			Identifier:    identifier,
			PackageIDs:    []uint{pkg.ID},
			PaymentMethod: model.PaymentMethodWallet,
		}, model.BuyerTypeAgent, shopID)
		if err != nil {
			resp.FailedCards = append(resp.FailedCards, buildFailedCard(cardNo, err))
			continue
		}

		resp.Orders = append(resp.Orders, dto.AgentOpenAPIWalletPackageOrderItem{
			CardNo:  cardNo,
			OrderNo: orderResp.OrderNo,
		})
	}

	resp.SuccessCount = len(resp.Orders)
	resp.FailedCount = len(resp.FailedCards)
	return resp, nil
}

func (s *Service) dispatchDeviceCardObservations(ctx context.Context, scene string, deviceID uint, syncType string) {
	if s.observationSeries == nil || deviceID == 0 {
		return
	}
	requestID := ""
	if value := middleware.GetRequestIDFromContext(ctx); value != nil {
		requestID = *value
	}
	s.observationSeries.DispatchDeviceCards(ctx, cardObservationApp.DeviceCardsSeriesRequest{
		DeviceID: deviceID,
		Request: cardObservationApp.SeriesRequest{
			Scene: scene, ResourceType: constants.CardObservationResourceTypeDevice,
			ResourceID: strconv.FormatUint(uint64(deviceID), 10), SyncType: syncType,
			Source:    constants.CardObservationSourceBusinessEvent,
			RequestID: requestID, CorrelationID: requestID,
		},
	})
}

func (s *Service) dispatchCardObservation(ctx context.Context, scene string, cardID uint, syncType string) {
	if s.observationSeries == nil || cardID == 0 {
		return
	}
	requestID := ""
	if value := middleware.GetRequestIDFromContext(ctx); value != nil {
		requestID = *value
	}
	s.observationSeries.Dispatch(ctx, cardObservationApp.SeriesRequest{
		Scene: scene, ResourceType: constants.CardObservationResourceTypeCard,
		ResourceID: strconv.FormatUint(uint64(cardID), 10), SyncType: syncType,
		Source:    constants.CardObservationSourceBusinessEvent,
		RequestID: requestID, CorrelationID: requestID,
	})
}

// resolveOpenAPIDevice 将开放接口设备标识解析为当前代理可见的设备
// 设备不存在或不属于代理管辖店铺时统一返回 CodeForbidden，避免信息泄露
func (s *Service) resolveOpenAPIDevice(ctx context.Context, deviceNo string) (*model.Device, error) {
	identifier := strings.TrimSpace(deviceNo)
	if identifier == "" {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	if s.deviceService == nil {
		return nil, errors.New(errors.CodeInternalError, "开放接口设备解析依赖未配置")
	}

	device, err := s.deviceService.GetDeviceByIdentifier(ctx, identifier)
	if err != nil {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
	}

	if device.ShopID == nil {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
	}

	subordinateIDs := middleware.GetSubordinateShopIDs(ctx)
	found := false
	for _, id := range subordinateIDs {
		if id == *device.ShopID {
			found = true
			break
		}
	}
	if !found {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
	}

	return device, nil
}

// GetDeviceTraffic 查询开放接口设备套餐内流量
func (s *Service) GetDeviceTraffic(ctx context.Context, req *dto.AgentOpenAPIDeviceQueryRequest) (*dto.AgentOpenAPIDeviceTrafficResponse, error) {
	device, err := s.resolveOpenAPIDevice(ctx, req.DeviceNo)
	if err != nil {
		return nil, err
	}

	activeStatus := constants.PackageUsageStatusActive
	pendingStatus := constants.PackageUsageStatusPending
	activeUsages, err := s.packageUsageStore.ListByCarrier(ctx, "device", device.ID, &activeStatus)
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询生效套餐失败")
	}
	pendingUsages, err := s.packageUsageStore.ListByCarrier(ctx, "device", device.ID, &pendingStatus)
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询待生效套餐失败")
	}

	packageMap, seriesMap, err := s.loadUsagePackageContext(ctx, append(activeUsages, pendingUsages...))
	if err != nil {
		return nil, err
	}

	resp := &dto.AgentOpenAPIDeviceTrafficResponse{
		DeviceNo:        req.DeviceNo,
		ActivePackages:  make([]dto.AgentOpenAPIPackageTrafficItem, 0, len(activeUsages)),
		PendingPackages: make([]dto.AgentOpenAPIPackageTrafficItem, 0, len(pendingUsages)),
	}
	for _, usage := range activeUsages {
		item := s.buildTrafficItem(usage, packageMap, seriesMap, true)
		resp.ActivePackages = append(resp.ActivePackages, item)
		resp.ActiveTotalFlowMB += item.TotalFlowMB
		resp.ActiveUsedFlowMB += item.UsedFlowMB
		resp.ActiveRemainingFlowMB += item.RemainingFlowMB
		if item.ExpiresAt != nil && (resp.ActiveExpiresAt == nil || item.ExpiresAt.After(*resp.ActiveExpiresAt)) {
			resp.ActiveExpiresAt = item.ExpiresAt
		}
	}
	for _, usage := range pendingUsages {
		resp.PendingPackages = append(resp.PendingPackages, s.buildTrafficItem(usage, packageMap, seriesMap, false))
	}

	s.dispatchDeviceCardObservations(ctx, constants.CardObservationSceneOpenDeviceTraffic, device.ID, constants.CardObservationSyncTypeTraffic)
	return resp, nil
}

// SwitchDeviceCard 开放接口切网（多卡设备切换到指定 ICCID）
func (s *Service) SwitchDeviceCard(ctx context.Context, req *dto.AgentOpenAPIDeviceSwitchCardRequest) error {
	if _, err := s.resolveOpenAPIDevice(ctx, req.DeviceNo); err != nil {
		return err
	}
	return s.deviceService.GatewaySwitchCard(ctx, req.DeviceNo, &dto.SwitchCardRequest{
		TargetICCID: req.ICCID,
	})
}

// RebootDevice 开放接口重启设备
func (s *Service) RebootDevice(ctx context.Context, req *dto.AgentOpenAPIDeviceOperationRequest) error {
	if _, err := s.resolveOpenAPIDevice(ctx, req.DeviceNo); err != nil {
		return err
	}
	return s.deviceService.GatewayRebootDevice(ctx, req.DeviceNo)
}

// ResetDevice 开放接口恢复出厂设置
func (s *Service) ResetDevice(ctx context.Context, req *dto.AgentOpenAPIDeviceOperationRequest) error {
	if _, err := s.resolveOpenAPIDevice(ctx, req.DeviceNo); err != nil {
		return err
	}
	return s.deviceService.GatewayResetDevice(ctx, req.DeviceNo)
}

// resolveOpenAPICard 将开放接口卡标识解析为当前代理可见的 IoT 卡
func (s *Service) resolveOpenAPICard(ctx context.Context, cardNo string) (*model.IotCard, error) {
	identifier := strings.TrimSpace(cardNo)
	if identifier == "" {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	if s.assetService == nil || s.iotCardStore == nil {
		return nil, errors.New(errors.CodeInternalError, "开放接口资产解析依赖未配置")
	}

	resolved, err := s.assetService.Resolve(ctx, identifier, false)
	if err != nil {
		if appErr, ok := err.(*errors.AppError); ok && appErr.Code == errors.CodeNotFound {
			return s.resolveOpenAPICardByIdentifier(ctx, identifier)
		}
		return nil, err
	}
	if resolved.AssetType != "card" {
		return nil, errors.New(errors.CodeInvalidParam, "开放接口只支持单卡标识")
	}

	card, err := s.iotCardStore.GetByID(ctx, resolved.AssetID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
		}
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡信息失败")
	}
	return card, nil
}

// resolveOpenAPICardByIdentifier 兜底支持 ICCID 双列、虚拟号和 MSISDN 精确解析
func (s *Service) resolveOpenAPICardByIdentifier(ctx context.Context, identifier string) (*model.IotCard, error) {
	card, err := s.iotCardStore.GetByIdentifier(ctx, identifier)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
		}
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡信息失败")
	}
	return card, nil
}

// loadUsagePackageContext 批量加载套餐和系列信息，避免单卡流量响应出现 N+1 查询
func (s *Service) loadUsagePackageContext(ctx context.Context, usages []*model.PackageUsage) (map[uint]*model.Package, map[uint]string, error) {
	packageIDs := make([]uint, 0)
	packageIDSeen := make(map[uint]struct{})
	for _, usage := range usages {
		if usage.PackageID == 0 {
			continue
		}
		if _, ok := packageIDSeen[usage.PackageID]; !ok {
			packageIDSeen[usage.PackageID] = struct{}{}
			packageIDs = append(packageIDs, usage.PackageID)
		}
	}

	packages, err := s.packageStore.GetByIDsUnscoped(ctx, packageIDs)
	if err != nil {
		return nil, nil, errors.Wrap(errors.CodeInternalError, err, "批量查询套餐失败")
	}
	packageMap := make(map[uint]*model.Package, len(packages))
	seriesIDSeen := make(map[uint]struct{})
	seriesIDs := make([]uint, 0)
	for _, pkg := range packages {
		packageMap[pkg.ID] = pkg
		if pkg.SeriesID > 0 {
			if _, ok := seriesIDSeen[pkg.SeriesID]; !ok {
				seriesIDSeen[pkg.SeriesID] = struct{}{}
				seriesIDs = append(seriesIDs, pkg.SeriesID)
			}
		}
	}

	seriesList, err := s.packageSeriesStore.GetByIDs(ctx, seriesIDs)
	if err != nil {
		return nil, nil, errors.Wrap(errors.CodeInternalError, err, "批量查询套餐系列失败")
	}
	seriesMap := make(map[uint]string, len(seriesList))
	for _, series := range seriesList {
		seriesMap[series.ID] = series.SeriesName
	}

	return packageMap, seriesMap, nil
}

// buildTrafficItem 按开放接口口径构造套餐流量项，主动隐藏虚流量内部字段
func (s *Service) buildTrafficItem(usage *model.PackageUsage, packageMap map[uint]*model.Package, seriesMap map[uint]string, active bool) dto.AgentOpenAPIPackageTrafficItem {
	pkg := packageMap[usage.PackageID]
	packageCode := ""
	packageName := usage.PackageName
	packageType := ""
	validDays := 0
	seriesName := ""
	if pkg != nil {
		packageCode = pkg.PackageCode
		if packageName == "" {
			packageName = pkg.PackageName
		}
		packageType = pkg.PackageType
		validDays = packageValidDays(nil, pkg.DurationMonths)
		if pkg.DurationDays > 0 {
			validDays = pkg.DurationDays
		}
		seriesName = seriesMap[pkg.SeriesID]
	}

	usedFlow := int64(0)
	remainingFlow := int64(0)
	if active {
		usedFlow = visibleUsedFlowMB(usage)
		remainingFlow = usage.DataLimitMB - usedFlow
		if remainingFlow < 0 {
			remainingFlow = 0
		}
	}

	return dto.AgentOpenAPIPackageTrafficItem{
		PackageCode:     packageCode,
		PackageName:     packageName,
		SeriesName:      seriesName,
		PackageType:     packageType,
		PackageTypeName: constants.AgentOpenAPIPackageTypeName(packageType),
		TotalFlowMB:     usage.DataLimitMB,
		UsedFlowMB:      usedFlow,
		RemainingFlowMB: remainingFlow,
		StartAt:         usage.ActivatedAt,
		ExpiresAt:       usage.ExpiresAt,
		ValidDays:       validDays,
		Priority:        usage.Priority,
	}
}

// visibleUsedFlowMB 计算对代理展示的已用流量，并限制不超过真实总量
func visibleUsedFlowMB(usage *model.PackageUsage) int64 {
	ratio := usage.DisplayGainRatioSnapshot
	if ratio <= 0 {
		ratio = 1
	}
	used := int64(float64(usage.DataUsageMB) * ratio)
	if used > usage.DataLimitMB {
		return usage.DataLimitMB
	}
	if used < 0 {
		return 0
	}
	return used
}

// packageValidDays 将套餐有效期统一折算为开放接口展示天数
func packageValidDays(durationDays *int, durationMonths int) int {
	if durationDays != nil && *durationDays > 0 {
		return *durationDays
	}
	if durationMonths > 0 {
		return durationMonths * 30
	}
	return 0
}

func normalizePage(page, pageSize int) (int, int) {
	if page < 1 {
		page = 1
	}
	if pageSize < 1 {
		pageSize = constants.DefaultPageSize
	}
	if pageSize > constants.MaxPageSize {
		pageSize = constants.MaxPageSize
	}
	return page, pageSize
}

func stringValue(value *string) string {
	if value == nil {
		return ""
	}
	return *value
}

func int64Value(value *int64) int64 {
	if value == nil {
		return 0
	}
	return *value
}

func walletCurrency(currency string) string {
	if currency == "" {
		return constants.AgentOpenAPIWalletCurrencyCNY
	}
	return currency
}

func buildFailedCard(cardNo string, err error) dto.AgentOpenAPIWalletPackageOrderFailure {
	code := errors.CodeInternalError
	message := errors.GetMessage(code, "zh-CN")
	if appErr, ok := err.(*errors.AppError); ok {
		code = appErr.Code
		message = appErr.Message
	}
	return dto.AgentOpenAPIWalletPackageOrderFailure{
		CardNo:  cardNo,
		Code:    code,
		Message: message,
	}
}

func generateAgentOpenAPIBatchNo() string {
	return fmt.Sprintf("%s%s%06d", constants.AgentOpenAPIBatchNoPrefix, time.Now().Format("20060102150405"), rand.Intn(1000000))
}
