package iot_card

import (
	"context"

	"github.com/break/junhong_cmp_fiber/internal/gateway"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	"github.com/break/junhong_cmp_fiber/internal/store"
	"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"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type Service struct {
	db                         *gorm.DB
	iotCardStore               *postgres.IotCardStore
	shopStore                  *postgres.ShopStore
	assetAllocationRecordStore *postgres.AssetAllocationRecordStore
	seriesAllocationStore      *postgres.ShopSeriesAllocationStore
	packageSeriesStore         *postgres.PackageSeriesStore
	gatewayClient              *gateway.Client
	logger                     *zap.Logger
}

func New(
	db *gorm.DB,
	iotCardStore *postgres.IotCardStore,
	shopStore *postgres.ShopStore,
	assetAllocationRecordStore *postgres.AssetAllocationRecordStore,
	seriesAllocationStore *postgres.ShopSeriesAllocationStore,
	packageSeriesStore *postgres.PackageSeriesStore,
	gatewayClient *gateway.Client,
	logger *zap.Logger,
) *Service {
	return &Service{
		db:                         db,
		iotCardStore:               iotCardStore,
		shopStore:                  shopStore,
		assetAllocationRecordStore: assetAllocationRecordStore,
		seriesAllocationStore:      seriesAllocationStore,
		packageSeriesStore:         packageSeriesStore,
		gatewayClient:              gatewayClient,
		logger:                     logger,
	}
}

func (s *Service) ListStandalone(ctx context.Context, req *dto.ListStandaloneIotCardRequest) (*dto.ListStandaloneIotCardResponse, error) {
	page := req.Page
	pageSize := req.PageSize
	if page == 0 {
		page = 1
	}
	if pageSize == 0 {
		pageSize = constants.DefaultPageSize
	}

	opts := &store.QueryOptions{
		Page:     page,
		PageSize: pageSize,
	}

	filters := make(map[string]interface{})
	if req.Status != nil {
		filters["status"] = *req.Status
	}
	if req.CarrierID != nil {
		filters["carrier_id"] = *req.CarrierID
	}
	if req.ShopID != nil {
		filters["shop_id"] = *req.ShopID
	}
	if req.ICCID != "" {
		filters["iccid"] = req.ICCID
	}
	if req.MSISDN != "" {
		filters["msisdn"] = req.MSISDN
	}
	if req.BatchNo != "" {
		filters["batch_no"] = req.BatchNo
	}
	if req.PackageID != nil {
		filters["package_id"] = *req.PackageID
	}
	if req.IsDistributed != nil {
		filters["is_distributed"] = *req.IsDistributed
	}
	if req.ICCIDStart != "" {
		filters["iccid_start"] = req.ICCIDStart
	}
	if req.ICCIDEnd != "" {
		filters["iccid_end"] = req.ICCIDEnd
	}
	if req.IsReplaced != nil {
		filters["is_replaced"] = *req.IsReplaced
	}
	if req.SeriesID != nil {
		filters["series_id"] = *req.SeriesID
	}

	cards, total, err := s.iotCardStore.ListStandalone(ctx, opts, filters)
	if err != nil {
		return nil, err
	}

	shopMap := s.loadShopNames(ctx, cards)

	list := make([]*dto.StandaloneIotCardResponse, 0, len(cards))
	for _, card := range cards {
		item := s.toStandaloneResponse(card, shopMap)
		list = append(list, item)
	}

	totalPages := int(total) / pageSize
	if int(total)%pageSize > 0 {
		totalPages++
	}

	return &dto.ListStandaloneIotCardResponse{
		List:       list,
		Total:      total,
		Page:       page,
		PageSize:   pageSize,
		TotalPages: totalPages,
	}, nil
}

// GetByICCID 通过 ICCID 获取单卡详情
func (s *Service) GetByICCID(ctx context.Context, iccid string) (*dto.IotCardDetailResponse, error) {
	card, err := s.iotCardStore.GetByICCID(ctx, iccid)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeNotFound, "IoT卡不存在")
		}
		return nil, err
	}

	shopMap := s.loadShopNames(ctx, []*model.IotCard{card})
	standaloneResp := s.toStandaloneResponse(card, shopMap)

	return &dto.IotCardDetailResponse{
		StandaloneIotCardResponse: *standaloneResp,
	}, nil
}

func (s *Service) loadShopNames(ctx context.Context, cards []*model.IotCard) map[uint]string {
	shopIDs := make([]uint, 0)
	shopIDSet := make(map[uint]bool)

	for _, card := range cards {
		if card.ShopID != nil && *card.ShopID > 0 && !shopIDSet[*card.ShopID] {
			shopIDs = append(shopIDs, *card.ShopID)
			shopIDSet[*card.ShopID] = true
		}
	}

	shopMap := make(map[uint]string)
	if len(shopIDs) > 0 {
		var shops []model.Shop
		s.db.WithContext(ctx).Where("id IN ?", shopIDs).Find(&shops)
		for _, shop := range shops {
			shopMap[shop.ID] = shop.ShopName
		}
	}

	return shopMap
}

func (s *Service) toStandaloneResponse(card *model.IotCard, shopMap map[uint]string) *dto.StandaloneIotCardResponse {
	resp := &dto.StandaloneIotCardResponse{
		ID:                  card.ID,
		ICCID:               card.ICCID,
		CardType:            card.CardType,
		CardCategory:        card.CardCategory,
		CarrierID:           card.CarrierID,
		CarrierType:         card.CarrierType,
		CarrierName:         card.CarrierName,
		IMSI:                card.IMSI,
		MSISDN:              card.MSISDN,
		BatchNo:             card.BatchNo,
		Supplier:            card.Supplier,
		CostPrice:           card.CostPrice,
		DistributePrice:     card.DistributePrice,
		Status:              card.Status,
		ShopID:              card.ShopID,
		ActivatedAt:         card.ActivatedAt,
		ActivationStatus:    card.ActivationStatus,
		RealNameStatus:      card.RealNameStatus,
		NetworkStatus:       card.NetworkStatus,
		DataUsageMB:         card.DataUsageMB,
		SeriesID:            card.SeriesID,
		FirstCommissionPaid: card.FirstCommissionPaid,
		AccumulatedRecharge: card.AccumulatedRecharge,
		CreatedAt:           card.CreatedAt,
		UpdatedAt:           card.UpdatedAt,
	}

	if card.ShopID != nil && *card.ShopID > 0 {
		resp.ShopName = shopMap[*card.ShopID]
	}

	return resp
}

func (s *Service) AllocateCards(ctx context.Context, req *dto.AllocateStandaloneCardsRequest, operatorID uint, operatorShopID *uint) (*dto.AllocateStandaloneCardsResponse, error) {
	if err := s.validateDirectSubordinate(ctx, operatorShopID, req.ToShopID); err != nil {
		return nil, err
	}

	cards, err := s.getCardsForAllocation(ctx, req, operatorShopID)
	if err != nil {
		return nil, err
	}

	if len(cards) == 0 {
		return &dto.AllocateStandaloneCardsResponse{
			TotalCount:   0,
			SuccessCount: 0,
			FailCount:    0,
			FailedItems:  []dto.AllocationFailedItem{},
		}, nil
	}

	var cardIDs []uint
	var failedItems []dto.AllocationFailedItem

	boundCardIDs, err := s.iotCardStore.GetBoundCardIDs(ctx, s.extractCardIDs(cards))
	if err != nil {
		return nil, err
	}
	boundCardIDSet := make(map[uint]bool)
	for _, id := range boundCardIDs {
		boundCardIDSet[id] = true
	}

	isPlatform := operatorShopID == nil

	for _, card := range cards {
		if boundCardIDSet[card.ID] {
			failedItems = append(failedItems, dto.AllocationFailedItem{
				ICCID:  card.ICCID,
				Reason: "已绑定设备的卡不能单独分配",
			})
			continue
		}

		if isPlatform && card.Status != constants.IotCardStatusInStock {
			failedItems = append(failedItems, dto.AllocationFailedItem{
				ICCID:  card.ICCID,
				Reason: "平台只能分配在库状态的卡",
			})
			continue
		}

		if !isPlatform && card.Status != constants.IotCardStatusDistributed {
			failedItems = append(failedItems, dto.AllocationFailedItem{
				ICCID:  card.ICCID,
				Reason: "代理只能分配已分销状态的卡",
			})
			continue
		}

		cardIDs = append(cardIDs, card.ID)
	}

	if len(cardIDs) == 0 {
		return &dto.AllocateStandaloneCardsResponse{
			TotalCount:   len(cards),
			SuccessCount: 0,
			FailCount:    len(failedItems),
			FailedItems:  failedItems,
		}, nil
	}

	newStatus := constants.IotCardStatusDistributed
	toShopID := req.ToShopID

	err = s.db.Transaction(func(tx *gorm.DB) error {
		txIotCardStore := postgres.NewIotCardStore(tx, nil)
		txRecordStore := postgres.NewAssetAllocationRecordStore(tx, nil)

		if err := txIotCardStore.BatchUpdateShopIDAndStatus(ctx, cardIDs, &toShopID, newStatus); err != nil {
			return err
		}

		allocationNo := s.assetAllocationRecordStore.GenerateAllocationNo(ctx, constants.AssetAllocationTypeAllocate)
		records := s.buildAllocationRecords(cards, cardIDs, operatorShopID, toShopID, operatorID, allocationNo, req.Remark)
		return txRecordStore.BatchCreate(ctx, records)
	})

	if err != nil {
		return nil, err
	}

	return &dto.AllocateStandaloneCardsResponse{
		TotalCount:   len(cards),
		SuccessCount: len(cardIDs),
		FailCount:    len(failedItems),
		AllocationNo: s.assetAllocationRecordStore.GenerateAllocationNo(ctx, constants.AssetAllocationTypeAllocate),
		FailedItems:  failedItems,
	}, nil
}

func (s *Service) RecallCards(ctx context.Context, req *dto.RecallStandaloneCardsRequest, operatorID uint, operatorShopID *uint) (*dto.RecallStandaloneCardsResponse, error) {
	if err := s.validateDirectSubordinate(ctx, operatorShopID, req.FromShopID); err != nil {
		return nil, err
	}

	cards, err := s.getCardsForRecall(ctx, req)
	if err != nil {
		return nil, err
	}

	if len(cards) == 0 {
		return &dto.RecallStandaloneCardsResponse{
			TotalCount:   0,
			SuccessCount: 0,
			FailCount:    0,
			FailedItems:  []dto.AllocationFailedItem{},
		}, nil
	}

	var cardIDs []uint
	var failedItems []dto.AllocationFailedItem

	boundCardIDs, err := s.iotCardStore.GetBoundCardIDs(ctx, s.extractCardIDs(cards))
	if err != nil {
		return nil, err
	}
	boundCardIDSet := make(map[uint]bool)
	for _, id := range boundCardIDs {
		boundCardIDSet[id] = true
	}

	for _, card := range cards {
		if boundCardIDSet[card.ID] {
			failedItems = append(failedItems, dto.AllocationFailedItem{
				ICCID:  card.ICCID,
				Reason: "已绑定设备的卡不能单独回收",
			})
			continue
		}

		if card.ShopID == nil || *card.ShopID != req.FromShopID {
			failedItems = append(failedItems, dto.AllocationFailedItem{
				ICCID:  card.ICCID,
				Reason: "卡不属于指定的店铺",
			})
			continue
		}

		cardIDs = append(cardIDs, card.ID)
	}

	if len(cardIDs) == 0 {
		return &dto.RecallStandaloneCardsResponse{
			TotalCount:   len(cards),
			SuccessCount: 0,
			FailCount:    len(failedItems),
			FailedItems:  failedItems,
		}, nil
	}

	isPlatform := operatorShopID == nil
	var newShopID *uint
	var newStatus int
	if isPlatform {
		newShopID = nil
		newStatus = constants.IotCardStatusInStock
	} else {
		newShopID = operatorShopID
		newStatus = constants.IotCardStatusDistributed
	}

	err = s.db.Transaction(func(tx *gorm.DB) error {
		txIotCardStore := postgres.NewIotCardStore(tx, nil)
		txRecordStore := postgres.NewAssetAllocationRecordStore(tx, nil)

		if err := txIotCardStore.BatchUpdateShopIDAndStatus(ctx, cardIDs, newShopID, newStatus); err != nil {
			return err
		}

		allocationNo := s.assetAllocationRecordStore.GenerateAllocationNo(ctx, constants.AssetAllocationTypeRecall)
		records := s.buildRecallRecords(cards, cardIDs, req.FromShopID, operatorShopID, operatorID, allocationNo, req.Remark)
		return txRecordStore.BatchCreate(ctx, records)
	})

	if err != nil {
		return nil, err
	}

	return &dto.RecallStandaloneCardsResponse{
		TotalCount:   len(cards),
		SuccessCount: len(cardIDs),
		FailCount:    len(failedItems),
		AllocationNo: s.assetAllocationRecordStore.GenerateAllocationNo(ctx, constants.AssetAllocationTypeRecall),
		FailedItems:  failedItems,
	}, nil
}

func (s *Service) validateDirectSubordinate(ctx context.Context, operatorShopID *uint, targetShopID uint) error {
	if operatorShopID != nil && *operatorShopID == targetShopID {
		return errors.ErrCannotAllocateToSelf
	}

	targetShop, err := s.shopStore.GetByID(ctx, targetShopID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return errors.New(errors.CodeShopNotFound)
		}
		return err
	}

	if operatorShopID == nil {
		if targetShop.ParentID != nil {
			return errors.ErrNotDirectSubordinate
		}
	} else {
		if targetShop.ParentID == nil || *targetShop.ParentID != *operatorShopID {
			return errors.ErrNotDirectSubordinate
		}
	}

	return nil
}

func (s *Service) getCardsForAllocation(ctx context.Context, req *dto.AllocateStandaloneCardsRequest, operatorShopID *uint) ([]*model.IotCard, error) {
	switch req.SelectionType {
	case dto.SelectionTypeList:
		return s.iotCardStore.GetByICCIDs(ctx, req.ICCIDs)
	case dto.SelectionTypeRange:
		return s.iotCardStore.GetStandaloneByICCIDRange(ctx, req.ICCIDStart, req.ICCIDEnd, operatorShopID)
	case dto.SelectionTypeFilter:
		filters := make(map[string]any)
		if req.CarrierID != nil {
			filters["carrier_id"] = *req.CarrierID
		}
		if req.BatchNo != "" {
			filters["batch_no"] = req.BatchNo
		}
		if req.Status != nil {
			filters["status"] = *req.Status
		}
		return s.iotCardStore.GetStandaloneByFilters(ctx, filters, operatorShopID)
	default:
		return nil, errors.New(errors.CodeInvalidParam, "无效的选卡方式")
	}
}

func (s *Service) getCardsForRecall(ctx context.Context, req *dto.RecallStandaloneCardsRequest) ([]*model.IotCard, error) {
	fromShopID := req.FromShopID
	switch req.SelectionType {
	case dto.SelectionTypeList:
		return s.iotCardStore.GetByICCIDs(ctx, req.ICCIDs)
	case dto.SelectionTypeRange:
		return s.iotCardStore.GetStandaloneByICCIDRange(ctx, req.ICCIDStart, req.ICCIDEnd, &fromShopID)
	case dto.SelectionTypeFilter:
		filters := make(map[string]any)
		if req.CarrierID != nil {
			filters["carrier_id"] = *req.CarrierID
		}
		if req.BatchNo != "" {
			filters["batch_no"] = req.BatchNo
		}
		return s.iotCardStore.GetStandaloneByFilters(ctx, filters, &fromShopID)
	default:
		return nil, errors.New(errors.CodeInvalidParam, "无效的选卡方式")
	}
}

func (s *Service) extractCardIDs(cards []*model.IotCard) []uint {
	ids := make([]uint, len(cards))
	for i, card := range cards {
		ids[i] = card.ID
	}
	return ids
}

func (s *Service) buildAllocationRecords(cards []*model.IotCard, successCardIDs []uint, fromShopID *uint, toShopID uint, operatorID uint, allocationNo, remark string) []*model.AssetAllocationRecord {
	successIDSet := make(map[uint]bool)
	for _, id := range successCardIDs {
		successIDSet[id] = true
	}

	var records []*model.AssetAllocationRecord
	for _, card := range cards {
		if !successIDSet[card.ID] {
			continue
		}

		record := &model.AssetAllocationRecord{
			AllocationNo:    allocationNo,
			AllocationType:  constants.AssetAllocationTypeAllocate,
			AssetType:       constants.AssetTypeIotCard,
			AssetID:         card.ID,
			AssetIdentifier: card.ICCID,
			ToOwnerType:     constants.OwnerTypeShop,
			ToOwnerID:       toShopID,
			OperatorID:      operatorID,
			Remark:          remark,
		}

		if fromShopID == nil {
			record.FromOwnerType = constants.OwnerTypePlatform
			record.FromOwnerID = nil
		} else {
			record.FromOwnerType = constants.OwnerTypeShop
			record.FromOwnerID = fromShopID
		}

		records = append(records, record)
	}

	return records
}

func (s *Service) buildRecallRecords(cards []*model.IotCard, successCardIDs []uint, fromShopID uint, toShopID *uint, operatorID uint, allocationNo, remark string) []*model.AssetAllocationRecord {
	successIDSet := make(map[uint]bool)
	for _, id := range successCardIDs {
		successIDSet[id] = true
	}

	var records []*model.AssetAllocationRecord
	for _, card := range cards {
		if !successIDSet[card.ID] {
			continue
		}

		record := &model.AssetAllocationRecord{
			AllocationNo:    allocationNo,
			AllocationType:  constants.AssetAllocationTypeRecall,
			AssetType:       constants.AssetTypeIotCard,
			AssetID:         card.ID,
			AssetIdentifier: card.ICCID,
			FromOwnerType:   constants.OwnerTypeShop,
			FromOwnerID:     &fromShopID,
			OperatorID:      operatorID,
			Remark:          remark,
		}

		if toShopID == nil {
			record.ToOwnerType = constants.OwnerTypePlatform
			record.ToOwnerID = 0
		} else {
			record.ToOwnerType = constants.OwnerTypeShop
			record.ToOwnerID = *toShopID
		}

		records = append(records, record)
	}

	return records
}

// BatchSetSeriesBinding 批量设置卡的套餐系列绑定
func (s *Service) BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetCardSeriesBindngRequest, operatorShopID *uint) (*dto.BatchSetCardSeriesBindngResponse, error) {
	cards, err := s.iotCardStore.GetByICCIDs(ctx, req.ICCIDs)
	if err != nil {
		return nil, err
	}

	if len(cards) == 0 {
		return &dto.BatchSetCardSeriesBindngResponse{
			SuccessCount: 0,
			FailCount:    len(req.ICCIDs),
			FailedItems:  s.buildCardNotFoundFailedItems(req.ICCIDs),
		}, nil
	}

	cardMap := make(map[string]*model.IotCard)
	for _, card := range cards {
		cardMap[card.ICCID] = card
	}

	// 验证系列存在（仅当 SeriesID > 0 时）
	var packageSeries *model.PackageSeries
	if req.SeriesID > 0 {
		packageSeries, err = s.packageSeriesStore.GetByID(ctx, req.SeriesID)
		if err != nil {
			if err == gorm.ErrRecordNotFound {
				return nil, errors.New(errors.CodeNotFound, "套餐系列不存在或已禁用")
			}
			return nil, err
		}
		if packageSeries.Status != 1 {
			return nil, errors.New(errors.CodeInvalidParam, "套餐系列不存在或已禁用")
		}
	}

	var successCardIDs []uint
	var failedItems []dto.CardSeriesBindngFailedItem

	for _, iccid := range req.ICCIDs {
		card, exists := cardMap[iccid]
		if !exists {
			failedItems = append(failedItems, dto.CardSeriesBindngFailedItem{
				ICCID:  iccid,
				Reason: "卡不存在",
			})
			continue
		}

		// 验证操作者权限（仅代理用户）
		if operatorShopID != nil && req.SeriesID > 0 {
			allocation, err := s.seriesAllocationStore.GetByShopAndSeries(ctx, *operatorShopID, req.SeriesID)
			if err != nil {
				if err == gorm.ErrRecordNotFound || allocation.Status != 1 {
					failedItems = append(failedItems, dto.CardSeriesBindngFailedItem{
						ICCID:  iccid,
						Reason: "您没有权限分配该套餐系列",
					})
					continue
				}
				return nil, err
			}
		}

		// 验证卡权限（基于 card.ShopID）
		if operatorShopID != nil {
			if card.ShopID == nil || *card.ShopID != *operatorShopID {
				failedItems = append(failedItems, dto.CardSeriesBindngFailedItem{
					ICCID:  iccid,
					Reason: "无权操作此卡",
				})
				continue
			}
		}

		successCardIDs = append(successCardIDs, card.ID)
	}

	if len(successCardIDs) > 0 {
		var seriesIDPtr *uint
		if req.SeriesID > 0 {
			seriesIDPtr = &req.SeriesID
		}
		if err := s.iotCardStore.BatchUpdateSeriesID(ctx, successCardIDs, seriesIDPtr); err != nil {
			return nil, err
		}
	}

	return &dto.BatchSetCardSeriesBindngResponse{
		SuccessCount: len(successCardIDs),
		FailCount:    len(failedItems),
		FailedItems:  failedItems,
	}, nil
}

func (s *Service) buildCardNotFoundFailedItems(iccids []string) []dto.CardSeriesBindngFailedItem {
	items := make([]dto.CardSeriesBindngFailedItem, len(iccids))
	for i, iccid := range iccids {
		items[i] = dto.CardSeriesBindngFailedItem{
			ICCID:  iccid,
			Reason: "卡不存在",
		}
	}
	return items
}

// SyncCardStatusFromGateway 从 Gateway 同步卡状态（示例方法）
func (s *Service) SyncCardStatusFromGateway(ctx context.Context, iccid string) error {
	if s.gatewayClient == nil {
		return errors.New(errors.CodeGatewayError, "Gateway 客户端未配置")
	}

	resp, err := s.gatewayClient.QueryCardStatus(ctx, &gateway.CardStatusReq{
		CardNo: iccid,
	})
	if err != nil {
		s.logger.Error("查询卡状态失败", zap.String("iccid", iccid), zap.Error(err))
		return errors.Wrap(errors.CodeGatewayError, err, "查询卡状态失败")
	}

	card, err := s.iotCardStore.GetByICCID(ctx, iccid)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return errors.New(errors.CodeNotFound, "IoT卡不存在")
		}
		return err
	}

	var newStatus int
	switch resp.CardStatus {
	case "准备":
		newStatus = constants.IotCardStatusInStock
	case "正常":
		newStatus = constants.IotCardStatusDistributed
	case "停机":
		newStatus = constants.IotCardStatusSuspended
	default:
		s.logger.Warn("未知的卡状态", zap.String("cardStatus", resp.CardStatus))
		return nil
	}

	if card.Status != newStatus {
		card.Status = newStatus
		if err := s.iotCardStore.Update(ctx, card); err != nil {
			return err
		}
		s.logger.Info("同步卡状态成功",
			zap.String("iccid", iccid),
			zap.Int("oldStatus", card.Status),
			zap.Int("newStatus", newStatus),
		)
	}

	return nil
}
