package cardobservation

import (
	"context"
	stderrors "errors"
	"strconv"
	"strings"
	"time"

	"gorm.io/gorm"

	cardapp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
	carddomain "github.com/break/junhong_cmp_fiber/internal/domain/cardobservation"
	"github.com/break/junhong_cmp_fiber/internal/gateway"
	"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/store/postgres"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	apperrors "github.com/break/junhong_cmp_fiber/pkg/errors"
)

// SeriesAttemptLogger 将未发送的序列结果写入统一 Integration Log。
type SeriesAttemptLogger struct {
	repository *integrationlog.Repository
}

// NewSeriesAttemptLogger 创建未发送尝试日志适配器。
func NewSeriesAttemptLogger(repository *integrationlog.Repository) *SeriesAttemptLogger {
	return &SeriesAttemptLogger{repository: repository}
}

// Record 写入互斥、限频或提前完成结果。
func (l *SeriesAttemptLogger) Record(ctx context.Context, payload cardapp.SeriesTaskPayload, result, reason string) error {
	if l == nil || l.repository == nil {
		return apperrors.New(apperrors.CodeInternalError, "卡观测 Integration Log 未配置")
	}
	resourceID := payload.ResourceID
	source, scene, seriesID := payload.Source, payload.Scene, payload.SeriesID
	requestID, correlationID := optionalText(payload.RequestID), optionalText(payload.CorrelationID)
	attempt, err := l.repository.Start(ctx, integrationlog.Attempt{
		IntegrationID: unsentIntegrationID(payload), Provider: constants.IntegrationProviderGateway,
		Direction: constants.IntegrationDirectionOutbound, Operation: operationForSyncType(payload.SyncType),
		ResourceType: payload.ResourceType, ResourceID: &resourceID, TriggerSource: &source,
		TriggerScene: &scene, TriggerSeries: &seriesID, ScheduledAt: &payload.ScheduledAt,
		Attempt: payload.Attempt, InitialResult: initialResult(result), RequestID: requestID, CorrelationID: correlationID,
		Metadata: map[string]any{"reason": reason, "sync_type": payload.SyncType},
	})
	if err != nil || result != constants.IntegrationResultFailed {
		return err
	}
	_, err = l.repository.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
		Result: constants.IntegrationResultFailed, ResponseSummary: map[string]any{"reason": reason},
	})
	return err
}

// RecordMerged 记录同场景重复触发被合并，不创建第二组三任务。
func (l *SeriesAttemptLogger) RecordMerged(ctx context.Context, request cardapp.SeriesRequest, seriesID string) error {
	resourceID := request.ResourceID
	source, scene := request.Source, request.Scene
	now := time.Now().UTC()
	_, err := l.repository.Start(ctx, integrationlog.Attempt{
		Provider: constants.IntegrationProviderGateway, Direction: constants.IntegrationDirectionOutbound,
		Operation: operationForSyncType(request.SyncType), ResourceType: request.ResourceType,
		ResourceID: &resourceID, TriggerSource: &source, TriggerScene: &scene, TriggerSeries: &seriesID,
		ScheduledAt: &now, Attempt: 1, InitialResult: constants.IntegrationResultMerged,
		RequestID: optionalText(request.RequestID), CorrelationID: optionalText(request.CorrelationID),
		Metadata: map[string]any{"reason": "同场景未结束序列已存在", "sync_type": request.SyncType},
	})
	return err
}

// SeriesRunner 负责卡本地预期判断、Gateway 查询和公共观测应用。
type SeriesRunner struct {
	db          *gorm.DB
	gateway     *gateway.Client
	observation *cardapp.Service
	integration *integrationlog.Repository
	carrier     *postgres.CarrierStore
}

// NewSeriesRunner 创建卡观测序列 Gateway 执行器。
func NewSeriesRunner(db *gorm.DB, gatewayClient *gateway.Client, observation *cardapp.Service, integration *integrationlog.Repository) *SeriesRunner {
	return &SeriesRunner{db: db, gateway: gatewayClient, observation: observation, integration: integration, carrier: postgres.NewCarrierStore(db)}
}

// Provider 返回用于请求互斥的运营商接入标识。
func (r *SeriesRunner) Provider(ctx context.Context, payload cardapp.SeriesTaskPayload) (string, error) {
	if payload.ResourceType == constants.CardObservationResourceTypeDevice {
		if _, err := r.loadDevice(ctx, payload); err != nil {
			return "", err
		}
		return constants.CardObservationResourceTypeDevice, nil
	}
	card, err := r.loadCard(ctx, payload)
	if err != nil {
		return "", err
	}
	provider := strings.ToLower(strings.TrimSpace(card.CarrierType))
	if provider == "" {
		provider = "carrier-" + strconv.FormatUint(uint64(card.CarrierID), 10)
	}
	return provider, nil
}

// ExpectationMet 在访问 Gateway 前读取本地权威快照。
func (r *SeriesRunner) ExpectationMet(ctx context.Context, payload cardapp.SeriesTaskPayload) (bool, error) {
	expected := strings.ToLower(strings.TrimSpace(payload.ExpectedValue))
	if expected == "" {
		return false, nil
	}
	if payload.ResourceType == constants.CardObservationResourceTypeDevice && payload.SyncType == constants.CardObservationSyncTypeDeviceInfo {
		currentICCIDs, err := r.loadCurrentICCIDs(ctx, payload)
		if err != nil {
			return false, err
		}
		for _, currentICCID := range currentICCIDs {
			if strings.EqualFold(expected, strings.TrimSpace(currentICCID)) {
				return true, nil
			}
		}
		return false, nil
	}
	card, err := r.loadCard(ctx, payload)
	if err != nil {
		return false, err
	}
	switch payload.SyncType {
	case constants.CardObservationSyncTypeRealname:
		return (expected == "verified" || expected == "1") && card.RealNameStatus == constants.RealNameStatusVerified, nil
	case constants.CardObservationSyncTypeNetwork:
		if expected == "online" || expected == "1" {
			return card.NetworkStatus == constants.NetworkStatusOnline, nil
		}
		if expected == "offline" || expected == "stopped" || expected == "0" {
			return card.NetworkStatus == constants.NetworkStatusOffline, nil
		}
	}
	return false, nil
}

// Run 执行一次 Gateway 查询，并将结果交给公共卡观测应用服务。
func (r *SeriesRunner) Run(ctx context.Context, payload cardapp.SeriesTaskPayload) (cardapp.RunResult, error) {
	if r == nil || r.db == nil || r.gateway == nil || r.observation == nil || r.integration == nil {
		return cardapp.RunResult{}, apperrors.New(apperrors.CodeInternalError, "卡观测序列 Gateway 能力未完整配置")
	}
	if payload.ResourceType == constants.CardObservationResourceTypeDevice && payload.SyncType == constants.CardObservationSyncTypeDeviceInfo {
		return r.runDeviceInfo(ctx, payload)
	}
	card, err := r.loadCard(ctx, payload)
	if err != nil {
		return cardapp.RunResult{}, err
	}
	attempt, err := r.startAttempt(ctx, payload, card)
	if err != nil {
		return cardapp.RunResult{}, err
	}
	startedAt := time.Now()
	result, runErr := r.queryAndApply(ctx, payload, card, attempt.IntegrationID)
	completionResult := constants.IntegrationResultSuccess
	if runErr != nil {
		completionResult = constants.IntegrationResultFailed
		if isRateLimited(runErr) {
			completionResult = constants.IntegrationResultRateLimited
			result.RateLimited = true
		}
	}
	_, completeErr := r.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
		Result: completionResult, DurationMS: time.Since(startedAt).Milliseconds(), StateChanged: result.StateChanged,
		ResponseSummary: map[string]any{"sync_type": payload.SyncType, "applied": runErr == nil},
	})
	if completeErr != nil {
		return result, completeErr
	}
	return result, runErr
}

func (r *SeriesRunner) runDeviceInfo(ctx context.Context, payload cardapp.SeriesTaskPayload) (cardapp.RunResult, error) {
	device, err := r.loadDevice(ctx, payload)
	if err != nil {
		return cardapp.RunResult{}, err
	}
	attempt, err := r.startDeviceAttempt(ctx, payload, device)
	if err != nil {
		return cardapp.RunResult{}, err
	}
	startedAt := time.Now()
	response, runErr := r.gateway.SyncDeviceInfo(ctx, &gateway.SyncDeviceInfoReq{CardNo: deviceGatewayIdentifier(device)})
	result := cardapp.RunResult{}
	if runErr == nil {
		result.StateChanged, runErr = r.applyDeviceInfo(ctx, device, response)
	}
	completionResult := constants.IntegrationResultSuccess
	if runErr != nil {
		completionResult = constants.IntegrationResultFailed
		if isRateLimited(runErr) {
			completionResult = constants.IntegrationResultRateLimited
			result.RateLimited = true
		}
	}
	_, completeErr := r.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
		Result: completionResult, DurationMS: time.Since(startedAt).Milliseconds(), StateChanged: result.StateChanged,
		ResponseSummary: map[string]any{"sync_type": payload.SyncType, "applied": runErr == nil},
	})
	if completeErr != nil {
		return result, completeErr
	}
	return result, runErr
}

func (r *SeriesRunner) startDeviceAttempt(ctx context.Context, payload cardapp.SeriesTaskPayload, device *model.Device) (*model.IntegrationLog, error) {
	resourceID, source, scene, seriesID := payload.ResourceID, payload.Source, payload.Scene, payload.SeriesID
	resourceKey := "device:" + strconv.FormatUint(uint64(device.ID), 10)
	return r.integration.Start(ctx, integrationlog.Attempt{
		IntegrationID: gatewayIntegrationID(payload), Provider: constants.IntegrationProviderGateway,
		Direction: constants.IntegrationDirectionOutbound, Operation: operationForSyncType(payload.SyncType),
		ResourceType: payload.ResourceType, ResourceID: &resourceID, ResourceKey: &resourceKey,
		TriggerSource: &source, TriggerScene: &scene, TriggerSeries: &seriesID,
		ScheduledAt: &payload.ScheduledAt, Attempt: payload.Attempt,
		RequestSummary: map[string]any{"device_id": device.ID, "sync_type": payload.SyncType},
		RequestID:      optionalText(payload.RequestID), CorrelationID: optionalText(payload.CorrelationID),
	})
}

func (r *SeriesRunner) applyDeviceInfo(ctx context.Context, device *model.Device, response *gateway.SyncDeviceInfoResp) (bool, error) {
	if response == nil {
		return false, apperrors.New(apperrors.CodeGatewayError, "Gateway 设备信息响应为空")
	}
	now := time.Now()
	updates := map[string]any{
		"online_status":        int(response.OnlineStatus),
		"software_version":     string(response.SoftwareVersion),
		"switch_mode":          string(response.SwitchMode),
		"last_gateway_sync_at": now,
	}
	if lastOnlineTime := parseGatewayTime(response.LastOnlineTime); lastOnlineTime != nil {
		updates["last_online_time"] = lastOnlineTime
	}
	var currentBinding struct {
		SlotPosition int
	}
	currentSlotErr := r.db.WithContext(ctx).Model(&model.DeviceSimBinding{}).
		Select("slot_position").
		Where("device_id = ? AND bind_status = ? AND is_current = ?", device.ID, constants.BindStatusBound, true).
		Take(&currentBinding).Error
	if currentSlotErr != nil && currentSlotErr != gorm.ErrRecordNotFound {
		return false, apperrors.Wrap(apperrors.CodeDatabaseError, currentSlotErr, "读取设备当前槽位失败")
	}
	changed := device.OnlineStatus != int(response.OnlineStatus) ||
		device.SoftwareVersion != string(response.SoftwareVersion) ||
		device.SwitchMode != string(response.SwitchMode) ||
		currentBinding.SlotPosition != int(response.CurrentSlotNo)
	err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Model(&model.Device{}).Where("id = ?", device.ID).Updates(updates).Error; err != nil {
			return err
		}
		if err := tx.Model(&model.DeviceSimBinding{}).
			Where("device_id = ? AND bind_status = ?", device.ID, constants.BindStatusBound).
			Update("is_current", false).Error; err != nil {
			return err
		}
		if int(response.CurrentSlotNo) <= 0 {
			return nil
		}
		return tx.Model(&model.DeviceSimBinding{}).
			Where("device_id = ? AND slot_position = ? AND bind_status = ?", device.ID, int(response.CurrentSlotNo), constants.BindStatusBound).
			Update("is_current", true).Error
	})
	if err != nil {
		return false, apperrors.Wrap(apperrors.CodeDatabaseError, err, "回写设备 Gateway 信息失败")
	}
	return changed, nil
}

func (r *SeriesRunner) loadDevice(ctx context.Context, payload cardapp.SeriesTaskPayload) (*model.Device, error) {
	if payload.ResourceType != constants.CardObservationResourceTypeDevice {
		return nil, apperrors.New(apperrors.CodeInvalidParam, "设备信息观测资源类型无效")
	}
	deviceID, err := strconv.ParseUint(payload.ResourceID, 10, 64)
	if err != nil || deviceID == 0 {
		return nil, apperrors.New(apperrors.CodeInvalidParam, "设备观测资源 ID 无效")
	}
	var device model.Device
	if err := r.db.WithContext(ctx).Where("id = ?", uint(deviceID)).First(&device).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, apperrors.New(apperrors.CodeNotFound, "设备不存在")
		}
		return nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询设备观测本地快照失败")
	}
	if deviceGatewayIdentifier(&device) == "" {
		return nil, apperrors.New(apperrors.CodeInvalidParam, "设备缺少 Gateway 标识")
	}
	return &device, nil
}

func (r *SeriesRunner) loadCurrentICCIDs(ctx context.Context, payload cardapp.SeriesTaskPayload) ([]string, error) {
	device, err := r.loadDevice(ctx, payload)
	if err != nil {
		return nil, err
	}
	var current struct {
		ICCID   string
		ICCID19 string
		ICCID20 *string
	}
	err = r.db.WithContext(ctx).Table("tb_device_sim_binding AS binding").
		Select("card.iccid, card.iccid_19, card.iccid_20").
		Joins("JOIN tb_iot_card AS card ON card.id = binding.iot_card_id AND card.deleted_at IS NULL").
		Where("binding.device_id = ? AND binding.bind_status = ? AND binding.is_current = ? AND binding.deleted_at IS NULL", device.ID, constants.BindStatusBound, true).
		Take(&current).Error
	if err == gorm.ErrRecordNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询设备当前卡失败")
	}
	iccids := []string{current.ICCID, current.ICCID19}
	if current.ICCID20 != nil {
		iccids = append(iccids, *current.ICCID20)
	}
	return iccids, nil
}

func deviceGatewayIdentifier(device *model.Device) string {
	if strings.TrimSpace(device.IMEI) != "" {
		return strings.TrimSpace(device.IMEI)
	}
	return strings.TrimSpace(device.SN)
}

func parseGatewayTime(raw gateway.FlexString) *time.Time {
	value := strings.TrimSpace(string(raw))
	if value == "" {
		return nil
	}
	if parsed, err := time.Parse(time.RFC3339, value); err == nil {
		return &parsed
	}
	timestamp, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		return nil
	}
	if timestamp > 1_000_000_000_000 {
		timestamp /= 1000
	}
	parsed := time.Unix(timestamp, 0)
	return &parsed
}

func (r *SeriesRunner) startAttempt(ctx context.Context, payload cardapp.SeriesTaskPayload, card *model.IotCard) (*model.IntegrationLog, error) {
	resourceID, source, scene, seriesID := payload.ResourceID, payload.Source, payload.Scene, payload.SeriesID
	resourceKey := "card:" + formatCardID(card.ID)
	return r.integration.Start(ctx, integrationlog.Attempt{
		IntegrationID: gatewayIntegrationID(payload), Provider: constants.IntegrationProviderGateway,
		Direction: constants.IntegrationDirectionOutbound, Operation: operationForSyncType(payload.SyncType),
		ResourceType: payload.ResourceType, ResourceID: &resourceID, ResourceKey: &resourceKey,
		TriggerSource: &source, TriggerScene: &scene, TriggerSeries: &seriesID,
		ScheduledAt: &payload.ScheduledAt, Attempt: payload.Attempt,
		RequestSummary: map[string]any{"card_id": card.ID, "sync_type": payload.SyncType},
		RequestID:      optionalText(payload.RequestID), CorrelationID: optionalText(payload.CorrelationID),
	})
}

func (r *SeriesRunner) queryAndApply(ctx context.Context, payload cardapp.SeriesTaskPayload, card *model.IotCard, observationID string) (cardapp.RunResult, error) {
	metadata := carddomain.ObservationMetadata{
		ObservationID: observationID, Source: constants.CardObservationSourceBusinessEvent,
		Scene: payload.Scene, ObservedAt: time.Now().UTC(), RequestID: payload.RequestID,
		CorrelationID: payload.CorrelationID,
	}
	switch payload.SyncType {
	case constants.CardObservationSyncTypeRealname:
		response, err := r.gateway.QueryRealnameStatus(ctx, &gateway.CardStatusReq{CardNo: card.ICCID})
		if err != nil {
			return cardapp.RunResult{}, err
		}
		decision, err := r.observation.ApplyCardObservation(ctx, carddomain.RealnameObservation{CardID: card.ID, Verified: response.RealStatus, Metadata: metadata})
		return cardapp.RunResult{StateChanged: decision.StatusChanged}, err
	case constants.CardObservationSyncTypeTraffic:
		response, err := r.gateway.QueryFlow(ctx, &gateway.FlowQueryReq{CardNo: card.ICCID})
		if err != nil {
			return cardapp.RunResult{}, err
		}
		decision, err := r.observation.ApplyTrafficObservation(ctx, carddomain.TrafficObservation{
			CardID: card.ID, GatewayReadingMB: float64(response.Used), ResetDay: r.carrier.GetDataResetDay(ctx, card.CarrierID), Metadata: metadata,
		})
		return cardapp.RunResult{StateChanged: decision.IncrementMB > 0 || decision.CrossMonth}, err
	case constants.CardObservationSyncTypeNetwork:
		response, err := r.gateway.QueryCardStatus(ctx, &gateway.CardStatusReq{CardNo: card.ICCID})
		if err != nil {
			return cardapp.RunResult{}, err
		}
		decision, err := r.observation.ApplyNetworkObservation(ctx, carddomain.NetworkObservation{
			CardID: card.ID, GatewayStatus: response.CardStatus, GatewayExtend: response.Extend,
			GatewayIMEI: response.IMEI, Metadata: metadata,
		})
		return cardapp.RunResult{StateChanged: decision.StatusChanged}, err
	default:
		return cardapp.RunResult{}, apperrors.New(apperrors.CodeInvalidParam, "设备信息观测执行器尚未注册")
	}
}

func (r *SeriesRunner) loadCard(ctx context.Context, payload cardapp.SeriesTaskPayload) (*model.IotCard, error) {
	if payload.ResourceType != constants.CardObservationResourceTypeCard {
		return nil, apperrors.New(apperrors.CodeInvalidParam, "当前观测执行器仅支持 IoT 卡资源")
	}
	cardID, err := strconv.ParseUint(payload.ResourceID, 10, 64)
	if err != nil || cardID == 0 {
		return nil, apperrors.New(apperrors.CodeInvalidParam, "卡观测资源 ID 无效")
	}
	var card model.IotCard
	if err := r.db.WithContext(ctx).Where("id = ?", uint(cardID)).First(&card).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, apperrors.New(apperrors.CodeNotFound, "IoT卡不存在")
		}
		return nil, apperrors.Wrap(apperrors.CodeDatabaseError, err, "查询卡观测本地快照失败")
	}
	return &card, nil
}

func isRateLimited(err error) bool {
	var appErr *apperrors.AppError
	if stderrors.As(err, &appErr) && appErr.Code == apperrors.CodeTooManyRequests {
		return true
	}
	return strings.Contains(err.Error(), "429") || strings.Contains(strings.ToLower(err.Error()), "rate limit")
}

func operationForSyncType(syncType string) string {
	switch syncType {
	case constants.CardObservationSyncTypeRealname:
		return constants.IntegrationOperationGatewayRealname
	case constants.CardObservationSyncTypeTraffic:
		return constants.IntegrationOperationGatewayTraffic
	case constants.CardObservationSyncTypeNetwork:
		return constants.IntegrationOperationGatewayNetwork
	default:
		return constants.IntegrationOperationGatewayDeviceInfo
	}
}

func gatewayIntegrationID(payload cardapp.SeriesTaskPayload) string {
	return "card-series:" + payload.SeriesID + ":" + strconv.Itoa(payload.Attempt)
}

func unsentIntegrationID(payload cardapp.SeriesTaskPayload) string {
	return gatewayIntegrationID(payload) + ":unsent"
}

func optionalText(value string) *string {
	value = strings.TrimSpace(value)
	if value == "" {
		return nil
	}
	return &value
}

func initialResult(result string) string {
	if result == constants.IntegrationResultFailed {
		return constants.IntegrationResultPending
	}
	return result
}

var _ cardapp.SeriesAttemptLogger = (*SeriesAttemptLogger)(nil)
var _ cardapp.SeriesRunner = (*SeriesRunner)(nil)
