package task

import (
	"context"
	"strconv"
	"time"

	"github.com/hibiken/asynq"
	"go.uber.org/zap"
	"gorm.io/gorm"

	cardObservationApp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
	"github.com/break/junhong_cmp_fiber/internal/gateway"
	"github.com/break/junhong_cmp_fiber/internal/model"
	iot_card_svc "github.com/break/junhong_cmp_fiber/internal/service/iot_card"
	"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"
)

// PollingProtectHandler 保护期一致性检查任务处理器
// 保护期内：直接调 Gateway 强制停/复机（绕过 EvaluateAndAct 三条件判断）
// 保护期结束：调 EvaluateAndAct 重新评估正常停复机条件
// 两种路径不可混淆：保护期内=强制修正；保护期结束=重新评估
type PollingProtectHandler struct {
	db                      *gorm.DB
	observationSeriesEvents cardObservationApp.SeriesEventWriter
	base                    *PollingBase
	gateway                 *gateway.Client
	iotCardStore            *postgres.IotCardStore
	deviceSimBindingStore   *postgres.DeviceSimBindingStore
	stopResumeSvc           iot_card_svc.StopResumeServiceInterface
}

// NewPollingProtectHandler 创建保护期一致性检查任务处理器
func NewPollingProtectHandler(
	db *gorm.DB,
	observationSeriesEvents cardObservationApp.SeriesEventWriter,
	base *PollingBase,
	gw *gateway.Client,
	iotCardStore *postgres.IotCardStore,
	deviceSimBindingStore *postgres.DeviceSimBindingStore,
	stopResumeSvc iot_card_svc.StopResumeServiceInterface,
) *PollingProtectHandler {
	return &PollingProtectHandler{
		db:                      db,
		observationSeriesEvents: observationSeriesEvents,
		base:                    base,
		gateway:                 gw,
		iotCardStore:            iotCardStore,
		deviceSimBindingStore:   deviceSimBindingStore,
		stopResumeSvc:           stopResumeSvc,
	}
}

// Handle 处理保护期一致性检查任务
func (h *PollingProtectHandler) Handle(ctx context.Context, t *asynq.Task) error {
	startTime := time.Now()

	cardID, ok := parseTaskPayload(t.Payload(), h.base.logger)
	if !ok {
		return nil
	}

	if !h.base.acquireConcurrency(ctx, constants.TaskTypePollingProtect) {
		h.base.logger.Debug("并发已满，重新入队", zap.Uint("card_id", cardID))
		return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
	}
	defer h.base.releaseConcurrency(ctx, constants.TaskTypePollingProtect)

	card, err := h.iotCardStore.GetByID(ctx, cardID)
	if err != nil {
		if isNotFound(err) {
			return nil
		}
		h.base.logger.Error("查询卡信息失败", zap.Uint("card_id", cardID), zap.Error(err))
		return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
	}

	// 独立卡（未绑设备）跳过保护期检查
	if card.IsStandalone {
		return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
	}

	// 未实名卡跳过保护期检查
	if card.RealNameStatus != constants.RealNameStatusVerified {
		return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
	}

	binding, err := h.deviceSimBindingStore.GetActiveBindingByCardID(ctx, card.ID)
	if err != nil || binding == nil {
		return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
	}
	deviceID := binding.DeviceID

	stopProtectGeneration := h.base.redis.Get(ctx, constants.RedisDeviceProtectKey(deviceID, "stop")).Val()
	startProtectGeneration := h.base.redis.Get(ctx, constants.RedisDeviceProtectKey(deviceID, "start")).Val()
	stopProtect := stopProtectGeneration != ""
	startProtect := startProtectGeneration != ""

	actionTaken := "no_action"

	switch {
	case stopProtect && card.NetworkStatus == constants.NetworkStatusOnline:
		// 保护期内：停机保护期发现开机卡 → 强制停机（绕过 EvaluateAndAct）
		h.base.logger.Info("保护期一致性：停机保护期内发现开机卡，强制停机",
			zap.Uint("card_id", card.ID), zap.Uint("device_id", deviceID))
		if h.gateway == nil {
			break
		}
		if err := h.gateway.StopCard(ctx, &gateway.CardOperationReq{CardNo: card.ICCID}); err != nil {
			h.base.logger.Error("保护期强制停机失败",
				zap.Uint("card_id", card.ID), zap.Error(err))
			h.base.updateStats(ctx, constants.TaskTypePollingProtect, false, time.Since(startTime))
			return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
		}
		if updateErr := h.updateCardAndAppendNetworkSeries(ctx, cardID, map[string]any{
			"network_status": constants.NetworkStatusOffline,
			"stopped_at":     time.Now(),
			"stop_reason":    constants.StopReasonProtectPeriod,
		}, constants.CardObservationSceneBusinessStop, "offline", "stop", stopProtectGeneration); updateErr != nil {
			h.base.logger.Warn("保护期停机 DB 更新失败", zap.Uint("card_id", cardID), zap.Error(updateErr))
			h.base.invalidateCardCache(ctx, cardID)
			h.base.updateStats(ctx, constants.TaskTypePollingProtect, false, time.Since(startTime))
			if requeueErr := h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect); requeueErr != nil {
				return errors.Wrap(errors.CodeInternalError, requeueErr, "保护期停机事务失败且重入队失败")
			}
			return updateErr
		}
		h.base.updateCardCache(ctx, cardID, map[string]any{"network_status": constants.NetworkStatusOffline})
		actionTaken = "forced_stop"

	case startProtect && card.NetworkStatus == constants.NetworkStatusOffline:
		// 保护期内：复机保护期发现停机卡 → 强制复机（绕过 EvaluateAndAct）
		h.base.logger.Info("保护期一致性：复机保护期内发现停机卡，强制复机",
			zap.Uint("card_id", card.ID), zap.Uint("device_id", deviceID))
		if h.gateway == nil {
			break
		}
		if err := h.gateway.StartCard(ctx, &gateway.CardOperationReq{CardNo: card.ICCID}); err != nil {
			h.base.logger.Error("保护期强制复机失败",
				zap.Uint("card_id", card.ID), zap.Error(err))
			h.base.updateStats(ctx, constants.TaskTypePollingProtect, false, time.Since(startTime))
			return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
		}
		if updateErr := h.updateCardAndAppendNetworkSeries(ctx, cardID, map[string]any{
			"network_status": constants.NetworkStatusOnline,
			"resumed_at":     time.Now(),
			"stop_reason":    "",
		}, constants.CardObservationSceneBusinessResume, "online", "start", startProtectGeneration); updateErr != nil {
			h.base.logger.Warn("保护期复机 DB 更新失败", zap.Uint("card_id", cardID), zap.Error(updateErr))
			h.base.invalidateCardCache(ctx, cardID)
			h.base.updateStats(ctx, constants.TaskTypePollingProtect, false, time.Since(startTime))
			if requeueErr := h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect); requeueErr != nil {
				return errors.Wrap(errors.CodeInternalError, requeueErr, "保护期复机事务失败且重入队失败")
			}
			return updateErr
		}
		h.base.updateCardCache(ctx, cardID, map[string]any{"network_status": constants.NetworkStatusOnline})
		actionTaken = "forced_resume"

	case !stopProtect && !startProtect:
		// 保护期结束：调 EvaluateAndAct 重新评估正常停复机条件
		if h.stopResumeSvc != nil {
			if evalErr := h.stopResumeSvc.EvaluateAndAct(ctx, card); evalErr != nil {
				h.base.logger.Warn("保护期结束后停复机评估失败",
					zap.Uint("card_id", cardID), zap.Error(evalErr))
			}
		}
		actionTaken = "evaluate"
	}

	if h.base.verboseLog {
		h.base.logger.Info("保护期检查详情",
			zap.Uint("card_id", cardID),
			zap.String("iccid", card.ICCID),
			zap.Bool("stop_protect", stopProtect),
			zap.Bool("start_protect", startProtect),
			zap.Int("network_status_at_check", card.NetworkStatus),
			zap.String("action_taken", actionTaken))
	}

	if updateErr := h.iotCardStore.UpdateFields(ctx, cardID, map[string]any{
		"last_protect_check_at": time.Now(),
	}); updateErr != nil {
		h.base.logger.Warn("更新保护期检查时间失败", zap.Uint("card_id", cardID), zap.Error(updateErr))
	}

	h.base.updateStats(ctx, constants.TaskTypePollingProtect, true, time.Since(startTime))
	return h.base.requeueCard(ctx, cardID, constants.TaskTypePollingProtect)
}

// updateCardAndAppendNetworkSeries 在同一事务中更新卡状态并写入网络观测请求。
func (h *PollingProtectHandler) updateCardAndAppendNetworkSeries(
	ctx context.Context,
	cardID uint,
	fields map[string]any,
	scene string,
	expected string,
	operation string,
	protectGeneration string,
) error {
	if h.db == nil || h.observationSeriesEvents == nil {
		return errors.New(errors.CodeInternalError, "保护期停复机观测 Outbox 能力未配置")
	}
	if protectGeneration == "" {
		return errors.New(errors.CodeInvalidParam, "保护期停复机事件缺少保护期标识")
	}
	requestID := "card-observation:polling-protect:" + operation + ":" +
		strconv.FormatUint(uint64(cardID), 10) + ":" + protectGeneration
	return h.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Model(&model.IotCard{}).Where("id = ?", cardID).Updates(fields).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "更新保护期停复机卡状态失败")
		}
		return h.observationSeriesEvents.AppendSeriesRequested(ctx, tx, cardObservationApp.SeriesRequestedEvent{
			EventID: requestID, Scene: scene,
			ResourceType: constants.CardObservationResourceTypeCard, ResourceID: cardID,
			SyncTypes: []string{constants.CardObservationSyncTypeNetwork}, ExpectedValue: expected,
			Source: constants.CardObservationSourceBusinessEvent, OccurredAt: time.Now().UTC(),
			RequestID: requestID, CorrelationID: requestID,
		})
	})
}
