package device

import (
	"context"

	"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"
	"gorm.io/gorm"
)

type Service struct {
	db                         *gorm.DB
	deviceStore                *postgres.DeviceStore
	deviceSimBindingStore      *postgres.DeviceSimBindingStore
	iotCardStore               *postgres.IotCardStore
	shopStore                  *postgres.ShopStore
	assetAllocationRecordStore *postgres.AssetAllocationRecordStore
	seriesAllocationStore      *postgres.ShopSeriesAllocationStore
	packageSeriesStore         *postgres.PackageSeriesStore
	shopSeriesAllocationStore  *postgres.ShopSeriesAllocationStore
}

func New(
	db *gorm.DB,
	deviceStore *postgres.DeviceStore,
	deviceSimBindingStore *postgres.DeviceSimBindingStore,
	iotCardStore *postgres.IotCardStore,
	shopStore *postgres.ShopStore,
	assetAllocationRecordStore *postgres.AssetAllocationRecordStore,
	seriesAllocationStore *postgres.ShopSeriesAllocationStore,
	packageSeriesStore *postgres.PackageSeriesStore,
) *Service {
	return &Service{
		db:                         db,
		deviceStore:                deviceStore,
		deviceSimBindingStore:      deviceSimBindingStore,
		iotCardStore:               iotCardStore,
		shopStore:                  shopStore,
		assetAllocationRecordStore: assetAllocationRecordStore,
		seriesAllocationStore:      seriesAllocationStore,
		packageSeriesStore:         packageSeriesStore,
		shopSeriesAllocationStore:  seriesAllocationStore,
	}
}

// List 获取设备列表
func (s *Service) List(ctx context.Context, req *dto.ListDeviceRequest) (*dto.ListDeviceResponse, 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.DeviceNo != "" {
		filters["device_no"] = req.DeviceNo
	}
	if req.DeviceName != "" {
		filters["device_name"] = req.DeviceName
	}
	if req.Status != nil {
		filters["status"] = *req.Status
	}
	if req.ShopID != nil {
		filters["shop_id"] = req.ShopID
	}
	if req.BatchNo != "" {
		filters["batch_no"] = req.BatchNo
	}
	if req.DeviceType != "" {
		filters["device_type"] = req.DeviceType
	}
	if req.Manufacturer != "" {
		filters["manufacturer"] = req.Manufacturer
	}
	if req.CreatedAtStart != nil {
		filters["created_at_start"] = *req.CreatedAtStart
	}
	if req.CreatedAtEnd != nil {
		filters["created_at_end"] = *req.CreatedAtEnd
	}
	if req.SeriesID != nil {
		filters["series_id"] = *req.SeriesID
	}

	devices, total, err := s.deviceStore.List(ctx, opts, filters)
	if err != nil {
		return nil, err
	}

	shopMap := s.loadShopData(ctx, devices)
	bindingCounts, err := s.getBindingCounts(ctx, s.extractDeviceIDs(devices))
	if err != nil {
		return nil, err
	}

	list := make([]*dto.DeviceResponse, 0, len(devices))
	for _, device := range devices {
		item := s.toDeviceResponse(device, shopMap, bindingCounts)
		list = append(list, item)
	}

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

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

// Get 获取设备详情
func (s *Service) Get(ctx context.Context, id uint) (*dto.DeviceResponse, error) {
	device, err := s.deviceStore.GetByID(ctx, id)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeNotFound, "设备不存在")
		}
		return nil, err
	}

	shopMap := s.loadShopData(ctx, []*model.Device{device})
	bindingCounts, err := s.getBindingCounts(ctx, []uint{device.ID})
	if err != nil {
		return nil, err
	}

	return s.toDeviceResponse(device, shopMap, bindingCounts), nil
}

// GetByDeviceNo 通过设备号获取设备详情
func (s *Service) GetByDeviceNo(ctx context.Context, deviceNo string) (*dto.DeviceResponse, error) {
	device, err := s.deviceStore.GetByDeviceNo(ctx, deviceNo)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeNotFound, "设备不存在")
		}
		return nil, err
	}

	shopMap := s.loadShopData(ctx, []*model.Device{device})
	bindingCounts, err := s.getBindingCounts(ctx, []uint{device.ID})
	if err != nil {
		return nil, err
	}

	return s.toDeviceResponse(device, shopMap, bindingCounts), nil
}

func (s *Service) Delete(ctx context.Context, id uint) error {
	device, err := s.deviceStore.GetByID(ctx, id)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return errors.New(errors.CodeNotFound, "设备不存在")
		}
		return err
	}

	if err := s.deviceSimBindingStore.UnbindByDeviceID(ctx, device.ID); err != nil {
		return err
	}

	return s.deviceStore.Delete(ctx, id)
}

// AllocateDevices 批量分配设备
func (s *Service) AllocateDevices(ctx context.Context, req *dto.AllocateDevicesRequest, operatorID uint, operatorShopID *uint) (*dto.AllocateDevicesResponse, error) {
	// 验证目标店铺是否为直属下级
	if err := s.validateDirectSubordinate(ctx, operatorShopID, req.TargetShopID); err != nil {
		return nil, err
	}

	devices, err := s.deviceStore.GetByIDs(ctx, req.DeviceIDs)
	if err != nil {
		return nil, err
	}

	if len(devices) == 0 {
		return &dto.AllocateDevicesResponse{
			SuccessCount: 0,
			FailCount:    0,
			FailedItems:  []dto.AllocationDeviceFailedItem{},
		}, nil
	}

	var deviceIDs []uint
	var failedItems []dto.AllocationDeviceFailedItem

	isPlatform := operatorShopID == nil

	for _, device := range devices {
		// 平台只能分配 shop_id=NULL 的设备
		if isPlatform && device.ShopID != nil {
			failedItems = append(failedItems, dto.AllocationDeviceFailedItem{
				DeviceID: device.ID,
				DeviceNo: device.DeviceNo,
				Reason:   "平台只能分配库存设备",
			})
			continue
		}

		// 代理只能分配自己店铺的设备
		if !isPlatform && (device.ShopID == nil || *device.ShopID != *operatorShopID) {
			failedItems = append(failedItems, dto.AllocationDeviceFailedItem{
				DeviceID: device.ID,
				DeviceNo: device.DeviceNo,
				Reason:   "设备不属于当前店铺",
			})
			continue
		}

		deviceIDs = append(deviceIDs, device.ID)
	}

	if len(deviceIDs) == 0 {
		return &dto.AllocateDevicesResponse{
			SuccessCount: 0,
			FailCount:    len(failedItems),
			FailedItems:  failedItems,
		}, nil
	}

	newStatus := constants.DeviceStatusDistributed
	targetShopID := req.TargetShopID

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

		if err := txDeviceStore.BatchUpdateShopIDAndStatus(ctx, deviceIDs, &targetShopID, newStatus); err != nil {
			return err
		}

		boundCardIDs, err := s.deviceSimBindingStore.GetBoundCardIDsByDeviceIDs(ctx, deviceIDs)
		if err != nil {
			return err
		}

		if len(boundCardIDs) > 0 {
			if err := txCardStore.BatchUpdateShopIDAndStatus(ctx, boundCardIDs, &targetShopID, constants.IotCardStatusDistributed); err != nil {
				return err
			}
		}

		allocationNo := s.assetAllocationRecordStore.GenerateAllocationNo(ctx, constants.AssetAllocationTypeAllocate)
		records := s.buildAllocationRecords(devices, deviceIDs, operatorShopID, targetShopID, operatorID, allocationNo, req.Remark)
		return txRecordStore.BatchCreate(ctx, records)
	})

	if err != nil {
		return nil, err
	}

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

// RecallDevices 批量回收设备
func (s *Service) RecallDevices(ctx context.Context, req *dto.RecallDevicesRequest, operatorID uint, operatorShopID *uint) (*dto.RecallDevicesResponse, error) {
	devices, err := s.deviceStore.GetByIDs(ctx, req.DeviceIDs)
	if err != nil {
		return nil, err
	}

	if len(devices) == 0 {
		return &dto.RecallDevicesResponse{
			SuccessCount: 0,
			FailCount:    0,
			FailedItems:  []dto.AllocationDeviceFailedItem{},
		}, nil
	}

	var deviceIDs []uint
	var failedItems []dto.AllocationDeviceFailedItem

	isPlatform := operatorShopID == nil

	for _, device := range devices {
		// 验证设备所属店铺是否为直属下级
		if device.ShopID == nil {
			failedItems = append(failedItems, dto.AllocationDeviceFailedItem{
				DeviceID: device.ID,
				DeviceNo: device.DeviceNo,
				Reason:   "设备已在平台库存中",
			})
			continue
		}

		// 验证直属下级关系
		if err := s.validateDirectSubordinate(ctx, operatorShopID, *device.ShopID); err != nil {
			failedItems = append(failedItems, dto.AllocationDeviceFailedItem{
				DeviceID: device.ID,
				DeviceNo: device.DeviceNo,
				Reason:   "只能回收直属下级店铺的设备",
			})
			continue
		}

		deviceIDs = append(deviceIDs, device.ID)
	}

	if len(deviceIDs) == 0 {
		return &dto.RecallDevicesResponse{
			SuccessCount: 0,
			FailCount:    len(failedItems),
			FailedItems:  failedItems,
		}, nil
	}

	var newShopID *uint
	var newStatus int
	if isPlatform {
		newShopID = nil
		newStatus = constants.DeviceStatusInStock
	} else {
		newShopID = operatorShopID
		newStatus = constants.DeviceStatusDistributed
	}

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

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

		boundCardIDs, err := s.deviceSimBindingStore.GetBoundCardIDsByDeviceIDs(ctx, deviceIDs)
		if err != nil {
			return err
		}

		if len(boundCardIDs) > 0 {
			var cardStatus int
			if isPlatform {
				cardStatus = constants.IotCardStatusInStock
			} else {
				cardStatus = constants.IotCardStatusDistributed
			}
			if err := txCardStore.BatchUpdateShopIDAndStatus(ctx, boundCardIDs, newShopID, cardStatus); err != nil {
				return err
			}
		}

		allocationNo := s.assetAllocationRecordStore.GenerateAllocationNo(ctx, constants.AssetAllocationTypeRecall)
		records := s.buildRecallRecords(devices, deviceIDs, operatorShopID, newShopID, operatorID, allocationNo, req.Remark)
		return txRecordStore.BatchCreate(ctx, records)
	})

	if err != nil {
		return nil, err
	}

	return &dto.RecallDevicesResponse{
		SuccessCount: len(deviceIDs),
		FailCount:    len(failedItems),
		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) loadShopData(ctx context.Context, devices []*model.Device) map[uint]string {
	shopIDs := make([]uint, 0)
	shopIDSet := make(map[uint]bool)

	for _, device := range devices {
		if device.ShopID != nil && *device.ShopID > 0 && !shopIDSet[*device.ShopID] {
			shopIDs = append(shopIDs, *device.ShopID)
			shopIDSet[*device.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) getBindingCounts(ctx context.Context, deviceIDs []uint) (map[uint]int64, error) {
	result := make(map[uint]int64)
	if len(deviceIDs) == 0 {
		return result, nil
	}

	bindings, err := s.deviceSimBindingStore.ListByDeviceIDs(ctx, deviceIDs)
	if err != nil {
		return nil, err
	}

	for _, binding := range bindings {
		result[binding.DeviceID]++
	}

	return result, nil
}

func (s *Service) extractDeviceIDs(devices []*model.Device) []uint {
	ids := make([]uint, len(devices))
	for i, device := range devices {
		ids[i] = device.ID
	}
	return ids
}

func (s *Service) toDeviceResponse(device *model.Device, shopMap map[uint]string, bindingCounts map[uint]int64) *dto.DeviceResponse {
	resp := &dto.DeviceResponse{
		ID:                  device.ID,
		DeviceNo:            device.DeviceNo,
		DeviceName:          device.DeviceName,
		DeviceModel:         device.DeviceModel,
		DeviceType:          device.DeviceType,
		MaxSimSlots:         device.MaxSimSlots,
		Manufacturer:        device.Manufacturer,
		BatchNo:             device.BatchNo,
		ShopID:              device.ShopID,
		Status:              device.Status,
		StatusName:          s.getDeviceStatusName(device.Status),
		BoundCardCount:      int(bindingCounts[device.ID]),
		SeriesID:            device.SeriesID,
		FirstCommissionPaid: device.FirstCommissionPaid,
		AccumulatedRecharge: device.AccumulatedRecharge,
		ActivatedAt:         device.ActivatedAt,
		CreatedAt:           device.CreatedAt,
		UpdatedAt:           device.UpdatedAt,
	}

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

	return resp
}

func (s *Service) getDeviceStatusName(status int) string {
	switch status {
	case constants.DeviceStatusInStock:
		return "在库"
	case constants.DeviceStatusDistributed:
		return "已分销"
	case constants.DeviceStatusActivated:
		return "已激活"
	case constants.DeviceStatusSuspended:
		return "已停用"
	default:
		return "未知"
	}
}

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

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

		record := &model.AssetAllocationRecord{
			AllocationNo:    allocationNo,
			AllocationType:  constants.AssetAllocationTypeAllocate,
			AssetType:       constants.AssetTypeDevice,
			AssetID:         device.ID,
			AssetIdentifier: device.DeviceNo,
			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(devices []*model.Device, successDeviceIDs []uint, fromShopID *uint, toShopID *uint, operatorID uint, allocationNo, remark string) []*model.AssetAllocationRecord {
	successIDSet := make(map[uint]bool)
	for _, id := range successDeviceIDs {
		successIDSet[id] = true
	}

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

		record := &model.AssetAllocationRecord{
			AllocationNo:    allocationNo,
			AllocationType:  constants.AssetAllocationTypeRecall,
			AssetType:       constants.AssetTypeDevice,
			AssetID:         device.ID,
			AssetIdentifier: device.DeviceNo,
			OperatorID:      operatorID,
			Remark:          remark,
		}

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

		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.BatchSetDeviceSeriesBindngRequest, operatorShopID *uint) (*dto.BatchSetDeviceSeriesBindngResponse, error) {
	devices, err := s.deviceStore.GetByIDs(ctx, req.DeviceIDs)
	if err != nil {
		return nil, err
	}

	if len(devices) == 0 {
		return &dto.BatchSetDeviceSeriesBindngResponse{
			SuccessCount: 0,
			FailCount:    len(req.DeviceIDs),
			FailedItems:  s.buildDeviceNotFoundFailedItems(req.DeviceIDs),
		}, nil
	}

	deviceMap := make(map[uint]*model.Device)
	for _, device := range devices {
		deviceMap[device.ID] = device
	}

	// 验证系列存在（仅当 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 successDeviceIDs []uint
	var failedItems []dto.DeviceSeriesBindngFailedItem

	for _, deviceID := range req.DeviceIDs {
		device, exists := deviceMap[deviceID]
		if !exists {
			failedItems = append(failedItems, dto.DeviceSeriesBindngFailedItem{
				DeviceID: deviceID,
				DeviceNo: "",
				Reason:   "设备不存在",
			})
			continue
		}

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

		// 验证设备权限（基于 device.ShopID）
		if operatorShopID != nil {
			if device.ShopID == nil || *device.ShopID != *operatorShopID {
				failedItems = append(failedItems, dto.DeviceSeriesBindngFailedItem{
					DeviceID: device.ID,
					DeviceNo: device.DeviceNo,
					Reason:   "无权操作此设备",
				})
				continue
			}
		}

		successDeviceIDs = append(successDeviceIDs, device.ID)
	}

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

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

func (s *Service) buildDeviceNotFoundFailedItems(deviceIDs []uint) []dto.DeviceSeriesBindngFailedItem {
	items := make([]dto.DeviceSeriesBindngFailedItem, len(deviceIDs))
	for i, id := range deviceIDs {
		items[i] = dto.DeviceSeriesBindngFailedItem{
			DeviceID: id,
			DeviceNo: "",
			Reason:   "设备不存在",
		}
	}
	return items
}
