package task

import (
	"context"
	stderrors "errors"
	"fmt"
	"path/filepath"
	"strings"
	"time"

	"github.com/bytedance/sonic"
	"github.com/hibiken/asynq"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"gorm.io/gorm"

	"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/postgres"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/storage"
	"github.com/break/junhong_cmp_fiber/pkg/utils"
)

const deviceBatchSize = 100

type DeviceImportPayload struct {
	TaskID uint `json:"task_id"`
}

// DeviceBatchAllocationExecutor 定义 Worker 复用现有设备分配与系列绑定规则的最小接口。
type DeviceBatchAllocationExecutor interface {
	AllocateDevices(ctx context.Context, req *dto.AllocateDevicesRequest, operatorID uint, operatorShopID *uint) (*dto.AllocateDevicesResponse, error)
	BatchSetSeriesBinding(ctx context.Context, req *dto.BatchSetDeviceSeriesBindngRequest, operatorShopID *uint) (*dto.BatchSetDeviceSeriesBindngResponse, error)
}

type DeviceImportHandler struct {
	db                    *gorm.DB
	redis                 *redis.Client
	importTaskStore       *postgres.DeviceImportTaskStore
	deviceStore           *postgres.DeviceStore
	deviceSimBindingStore *postgres.DeviceSimBindingStore
	iotCardStore          *postgres.IotCardStore
	assetWalletStore      *postgres.AssetWalletStore
	assetIdentifierStore  *postgres.AssetIdentifierStore
	storageService        *storage.Service
	logger                *zap.Logger
	allocationExecutor    DeviceBatchAllocationExecutor
}

func NewDeviceImportHandler(
	db *gorm.DB,
	redis *redis.Client,
	importTaskStore *postgres.DeviceImportTaskStore,
	deviceStore *postgres.DeviceStore,
	deviceSimBindingStore *postgres.DeviceSimBindingStore,
	iotCardStore *postgres.IotCardStore,
	assetWalletStore *postgres.AssetWalletStore,
	assetIdentifierStore *postgres.AssetIdentifierStore,
	storageSvc *storage.Service,
	logger *zap.Logger,
	allocationExecutor DeviceBatchAllocationExecutor,
) *DeviceImportHandler {
	return &DeviceImportHandler{
		db:                    db,
		redis:                 redis,
		importTaskStore:       importTaskStore,
		deviceStore:           deviceStore,
		deviceSimBindingStore: deviceSimBindingStore,
		iotCardStore:          iotCardStore,
		assetWalletStore:      assetWalletStore,
		assetIdentifierStore:  assetIdentifierStore,
		storageService:        storageSvc,
		logger:                logger,
		allocationExecutor:    allocationExecutor,
	}
}

func (h *DeviceImportHandler) HandleDeviceImport(ctx context.Context, task *asynq.Task) error {
	var payload DeviceImportPayload
	if err := sonic.Unmarshal(task.Payload(), &payload); err != nil {
		h.logger.Error("解析设备导入任务载荷失败",
			zap.Error(err),
			zap.String("task_id", task.ResultWriter().TaskID()),
		)
		return asynq.SkipRetry
	}

	importTask, err := h.importTaskStore.GetByID(ctx, payload.TaskID)
	if err != nil {
		h.logger.Error("获取导入任务失败",
			zap.Uint("task_id", payload.TaskID),
			zap.Error(err),
		)
		return asynq.SkipRetry
	}

	switch importTask.Status {
	case model.ImportTaskStatusPending:
		// 正常首次处理
	case model.ImportTaskStatusProcessing:
		// 上次 worker 中途中断（重启/崩溃），重置计数重新处理
		// 已入库的设备会被 ExistsByDeviceNoBatch 识别为 skip，不会重复写入
		h.logger.Warn("检测到导入任务上次处理中途中断，重置进度重新处理",
			zap.Uint("task_id", payload.TaskID),
		)
		h.importTaskStore.UpdateProgress(ctx, importTask.ID, 0, 0, 0)
	default:
		// 已完成或失败，不重复处理
		h.logger.Info("导入任务已处理，跳过",
			zap.Uint("task_id", payload.TaskID),
			zap.Int("status", importTask.Status),
		)
		return nil
	}

	h.importTaskStore.UpdateStatus(ctx, importTask.ID, model.ImportTaskStatusProcessing, "")

	h.logger.Info("开始处理设备导入任务",
		zap.Uint("task_id", importTask.ID),
		zap.String("task_no", importTask.TaskNo),
		zap.String("storage_key", importTask.StorageKey),
	)
	if importTask.OperationType != "" && importTask.OperationType != constants.DeviceImportOperationCreate {
		return h.handleDeviceBatchAllocation(ctx, importTask)
	}

	parseResult, err := h.downloadAndParse(ctx, importTask)
	if err != nil {
		h.logger.Error("下载或解析 Excel 失败",
			zap.Uint("task_id", importTask.ID),
			zap.Error(err),
		)
		h.importTaskStore.UpdateStatus(ctx, importTask.ID, model.ImportTaskStatusFailed, err.Error())
		return asynq.SkipRetry
	}

	result := h.processImport(ctx, importTask, parseResult.Rows)

	for _, pe := range parseResult.ParseErrors {
		result.failedItems = append(result.failedItems, model.ImportResultItem{
			Line:   pe.Line,
			ICCID:  pe.ICCID,
			Reason: pe.Reason,
		})
		result.failCount++
	}

	h.importTaskStore.UpdateResult(ctx, importTask.ID, parseResult.TotalCount, result.successCount, result.skipCount, result.failCount, 0, result.skippedItems, result.failedItems, nil)

	if result.failCount > 0 && result.successCount == 0 {
		h.importTaskStore.UpdateStatus(ctx, importTask.ID, model.ImportTaskStatusFailed, "所有导入均失败")
	} else {
		h.importTaskStore.UpdateStatus(ctx, importTask.ID, model.ImportTaskStatusCompleted, "")
	}

	h.logger.Info("设备导入任务完成",
		zap.Uint("task_id", importTask.ID),
		zap.Int("success_count", result.successCount),
		zap.Int("skip_count", result.skipCount),
		zap.Int("fail_count", result.failCount),
	)

	return nil
}

func (h *DeviceImportHandler) downloadAndParse(ctx context.Context, task *model.DeviceImportTask) (*utils.DeviceParseResult, error) {
	if h.storageService == nil {
		return nil, ErrStorageNotConfigured
	}

	if task.StorageKey == "" {
		return nil, ErrStorageKeyEmpty
	}

	localPath, cleanup, err := h.storageService.DownloadToTemp(ctx, task.StorageKey)
	if err != nil {
		return nil, err
	}
	defer cleanup()

	if !strings.HasSuffix(strings.ToLower(task.FileName), ".xlsx") {
		ext := filepath.Ext(task.FileName)
		return nil, fmt.Errorf("不支持的文件格式 %s,请上传Excel文件(.xlsx)", ext)
	}

	return utils.ParseDeviceExcel(localPath)
}

type deviceImportResult struct {
	successCount int
	skipCount    int
	failCount    int
	skippedItems model.ImportResultItems
	failedItems  model.ImportResultItems
}

func (h *DeviceImportHandler) processImport(ctx context.Context, task *model.DeviceImportTask, rows []utils.DeviceRow) *deviceImportResult {
	result := &deviceImportResult{
		skippedItems: make(model.ImportResultItems, 0),
		failedItems:  make(model.ImportResultItems, 0),
	}

	if len(rows) == 0 {
		return result
	}

	for i := 0; i < len(rows); i += deviceBatchSize {
		end := min(i+deviceBatchSize, len(rows))
		batch := rows[i:end]
		h.processBatch(ctx, task, batch, result)
		// 每批完成后实时更新进度计数，让前端可以看到处理进度
		h.importTaskStore.UpdateProgress(ctx, task.ID, result.successCount, result.skipCount, result.failCount)
	}

	return result
}

func (h *DeviceImportHandler) processBatch(ctx context.Context, task *model.DeviceImportTask, batch []utils.DeviceRow, result *deviceImportResult) {
	deviceNos := make([]string, 0, len(batch))
	allICCIDs := make([]string, 0)

	for _, row := range batch {
		deviceNos = append(deviceNos, row.VirtualNo)
		for _, slotICCID := range row.SlotICCIDs {
			allICCIDs = append(allICCIDs, slotICCID.ICCID)
		}
	}

	existingDevices, err := h.deviceStore.ExistsByDeviceNoBatch(ctx, deviceNos)
	if err != nil {
		h.logger.Error("检查设备是否存在失败", zap.Error(err))
		for _, row := range batch {
			result.failedItems = append(result.failedItems, model.ImportResultItem{
				Line:   row.Line,
				ICCID:  row.VirtualNo,
				Reason: "数据库查询失败",
			})
			result.failCount++
		}
		return
	}

	var existingCards map[string]*model.IotCard
	var boundCards map[string]bool
	if len(allICCIDs) > 0 {
		cards, err := h.iotCardStore.GetByICCIDs(ctx, allICCIDs)
		if err != nil {
			h.logger.Error("查询卡信息失败", zap.Error(err))
		} else {
			existingCards = make(map[string]*model.IotCard)
			for _, card := range cards {
				existingCards[card.ICCID] = card
			}
		}

		boundCards, err = h.deviceSimBindingStore.GetBoundICCIDs(ctx, allICCIDs)
		if err != nil {
			h.logger.Error("查询卡绑定状态失败", zap.Error(err))
		}
	}

	for _, row := range batch {
		if existingDevices[row.VirtualNo] {
			result.skippedItems = append(result.skippedItems, model.ImportResultItem{
				Line:   row.Line,
				ICCID:  row.VirtualNo,
				Reason: "设备号已存在",
			})
			result.skipCount++
			continue
		}

		rowFailed := false
		for _, slotICCID := range row.SlotICCIDs {
			if slotICCID.SlotPosition <= row.MaxSimSlots {
				continue
			}

			result.failedItems = append(result.failedItems, model.ImportResultItem{
				Line:   row.Line,
				ICCID:  row.VirtualNo,
				Reason: "卡槽填写超出最大SIM槽数",
			})
			result.failCount++
			rowFailed = true
			break
		}
		if rowFailed {
			continue
		}

		var validCards []utils.DeviceSlotICCID
		var cardIssues []string

		for _, slotICCID := range row.SlotICCIDs {
			card, exists := existingCards[slotICCID.ICCID]
			if !exists {
				cardIssues = append(cardIssues, slotICCID.ICCID+"不存在")
				continue
			}
			if boundCards[slotICCID.ICCID] {
				cardIssues = append(cardIssues, slotICCID.ICCID+"已绑定其他设备")
				continue
			}
			if card.ShopID != nil {
				cardIssues = append(cardIssues, slotICCID.ICCID+"已分配给店铺,不能绑定到平台库存设备")
				continue
			}
			validCards = append(validCards, utils.DeviceSlotICCID{
				SlotPosition: slotICCID.SlotPosition,
				ICCID:        slotICCID.ICCID,
			})
		}

		if len(row.SlotICCIDs) > 0 && len(cardIssues) > 0 {
			result.failedItems = append(result.failedItems, model.ImportResultItem{
				Line:   row.Line,
				ICCID:  row.VirtualNo,
				Reason: "卡验证失败: " + strings.Join(cardIssues, ", "),
			})
			result.failCount++
			continue
		}

		err := h.db.Transaction(func(tx *gorm.DB) error {
			txDeviceStore := postgres.NewDeviceStore(tx, nil)
			txBindingStore := postgres.NewDeviceSimBindingStore(tx, nil)
			txIotCardStore := postgres.NewIotCardStore(tx, nil)

			device := &model.Device{
				VirtualNo:      row.VirtualNo,
				SN:             row.SN,
				IMEI:           row.IMEI,
				DeviceName:     row.DeviceName,
				DeviceModel:    row.DeviceModel,
				DeviceType:     row.DeviceType,
				MaxSimSlots:    row.MaxSimSlots,
				Manufacturer:   row.Manufacturer,
				BatchNo:        task.BatchNo,
				Status:         constants.DeviceStatusInStock,
				RealnamePolicy: task.RealnamePolicy,
			}
			device.Creator = task.Creator
			device.Updater = task.Creator

			if err := txDeviceStore.Create(ctx, device); err != nil {
				return err
			}

			txIdentifierStore := postgres.NewAssetIdentifierStore(tx)
			if err := txIdentifierStore.Register(ctx, device.VirtualNo, model.AssetTypeDevice, device.ID); err != nil {
				return fmt.Errorf("虚拟号已被占用: %s", device.VirtualNo)
			}

			now := time.Now()
			validCardIDs := make([]uint, 0, len(validCards))
			for _, slotICCID := range validCards {
				cardID := existingCards[slotICCID.ICCID].ID
				validCardIDs = append(validCardIDs, cardID)

				binding := &model.DeviceSimBinding{
					DeviceID:     device.ID,
					IotCardID:    cardID,
					SlotPosition: slotICCID.SlotPosition,
					BindStatus:   1,
					BindTime:     &now,
				}
				if err := txBindingStore.Create(ctx, binding); err != nil {
					return err
				}
			}

			// 批量更新卡的设备虚拟号快照（与绑定记录在同一事务中执行）
			if len(validCardIDs) > 0 {
				if err := txIotCardStore.BatchUpdateDeviceVirtualNo(ctx, validCardIDs, device.VirtualNo); err != nil {
					return err
				}
			}

			txWalletStore := postgres.NewAssetWalletStore(tx, nil)
			deviceWallet := &model.AssetWallet{
				ResourceType:  constants.AssetWalletResourceTypeDevice,
				ResourceID:    device.ID,
				Balance:       0,
				FrozenBalance: 0,
				Currency:      "CNY",
				Status:        constants.AssetWalletStatusNormal,
				Version:       0,
			}
			if err := txWalletStore.Create(ctx, deviceWallet); err != nil {
				return err
			}

			return nil
		})

		if err != nil {
			h.logger.Error("创建设备失败",
				zap.String("virtual_no", row.VirtualNo),
				zap.Error(err),
			)
			result.failedItems = append(result.failedItems, model.ImportResultItem{
				Line:   row.Line,
				ICCID:  row.VirtualNo,
				Reason: "数据库写入失败: " + err.Error(),
			})
			result.failCount++
			continue
		}
		if len(validCards) > 0 && h.iotCardStore != nil {
			h.iotCardStore.InvalidateListCountCache(ctx)
		}

		for _, slotICCID := range row.SlotICCIDs {
			if card, exists := existingCards[slotICCID.ICCID]; exists && !boundCards[slotICCID.ICCID] && card.ShopID == nil {
				boundCards[slotICCID.ICCID] = true
			}
		}

		result.successCount++
	}
}

var ErrMissingDeviceNoColumn = stderrors.New("CSV 缺少 virtual_no 列")
