package cardobservation

import (
	"context"
	"strconv"
	"time"

	"github.com/bytedance/sonic"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"

	cardapp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
	"github.com/break/junhong_cmp_fiber/internal/infrastructure/cardtrafficlock"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

// SeriesCoordinator 使用 Redis 原子协调活跃序列、幂等尝试和实际请求互斥。
type SeriesCoordinator struct {
	redis       *redis.Client
	trafficLock *cardtrafficlock.Lock
	now         func() time.Time
}

// ClaimSchedule 保证每个固定阶梯任务只成功入队一次。
func (c *SeriesCoordinator) ClaimSchedule(ctx context.Context, seriesID string, attempt int) (bool, error) {
	claimed, err := c.redis.SetNX(ctx, constants.RedisCardObservationScheduleKey(seriesID, attempt), "scheduled", constants.CardObservationSeriesResultTTL).Result()
	if err != nil {
		return false, errors.Wrap(errors.CodeInternalError, err, "领取卡观测序列入队槽位失败")
	}
	return claimed, nil
}

// IsScheduled 判断稳定业务事件是否已经创建过第一阶梯任务。
func (c *SeriesCoordinator) IsScheduled(ctx context.Context, seriesID string) (bool, error) {
	keys := make([]string, 0, constants.CardObservationSeriesAttemptCount)
	for attempt := 1; attempt <= constants.CardObservationSeriesAttemptCount; attempt++ {
		keys = append(keys, constants.RedisCardObservationScheduleKey(seriesID, attempt))
	}
	exists, err := c.redis.Exists(ctx, keys...).Result()
	if err != nil {
		return false, errors.Wrap(errors.CodeInternalError, err, "读取卡观测序列业务幂等状态失败")
	}
	return exists == int64(constants.CardObservationSeriesAttemptCount), nil
}

// ReleaseSchedule 在入队失败时释放槽位，允许后续同场景触发补齐原序列。
func (c *SeriesCoordinator) ReleaseSchedule(ctx context.Context, seriesID string, attempt int) {
	_ = c.redis.Del(ctx, constants.RedisCardObservationScheduleKey(seriesID, attempt)).Err()
}

// NewSeriesCoordinator 创建观测序列 Redis 协调器。
func NewSeriesCoordinator(redisClient *redis.Client) *SeriesCoordinator {
	return &SeriesCoordinator{redis: redisClient, trafficLock: cardtrafficlock.New(redisClient), now: time.Now}
}

// Reserve 原子保留同场景活跃序列；重复触发不会刷新 TTL。
func (c *SeriesCoordinator) Reserve(ctx context.Context, request cardapp.SeriesRequest, candidateSeriesID string, candidateBaseTime time.Time) (string, time.Time, cardapp.SeriesRequest, bool, error) {
	if c == nil || c.redis == nil {
		return "", time.Time{}, cardapp.SeriesRequest{}, false, errors.New(errors.CodeInternalError, "卡观测序列 Redis 未配置")
	}
	requestJSON, err := sonic.Marshal(request)
	if err != nil {
		return "", time.Time{}, cardapp.SeriesRequest{}, false, errors.Wrap(errors.CodeInvalidParam, err, "序列原始请求无法保存")
	}
	seriesKey := constants.RedisCardObservationSeriesKey(request.Scene, request.ResourceType, request.ResourceID, request.SyncType)
	indexKey := constants.RedisCardObservationSeriesIndexKey(request.ResourceType, request.ResourceID, request.SyncType)
	result, err := c.redis.Eval(ctx, `
local current = redis.call('HMGET', KEYS[1], 'series_id', 'base_time_ms', 'request_json')
if current[1] then return {current[1], current[2], current[3], '1'} end
redis.call('HSET', KEYS[1], 'series_id', ARGV[1], 'base_time_ms', ARGV[2], 'request_json', ARGV[3])
redis.call('EXPIRE', KEYS[1], ARGV[4])
redis.call('SADD', KEYS[2], KEYS[1])
	redis.call('EXPIRE', KEYS[2], ARGV[4])
return {ARGV[1], ARGV[2], ARGV[3], '0'}
`, []string{seriesKey, indexKey}, candidateSeriesID, candidateBaseTime.UTC().UnixMilli(), string(requestJSON), int(constants.CardObservationSeriesTTL.Seconds())).StringSlice()
	if err != nil || len(result) != 4 {
		return "", time.Time{}, cardapp.SeriesRequest{}, false, errors.Wrap(errors.CodeInternalError, err, "保留卡观测序列失败")
	}
	baseTimeMS, parseErr := strconv.ParseInt(result[1], 10, 64)
	if parseErr != nil {
		return "", time.Time{}, cardapp.SeriesRequest{}, false, errors.Wrap(errors.CodeInternalError, parseErr, "解析卡观测序列基准时间失败")
	}
	var originalRequest cardapp.SeriesRequest
	if unmarshalErr := sonic.Unmarshal([]byte(result[2]), &originalRequest); unmarshalErr != nil {
		return "", time.Time{}, cardapp.SeriesRequest{}, false, errors.Wrap(errors.CodeInternalError, unmarshalErr, "解析序列原始请求失败")
	}
	return result[0], time.UnixMilli(baseTimeMS).UTC(), originalRequest, result[3] == "1", nil
}

// IsCompleted 判断序列是否已由预期状态或回调提前完成。
func (c *SeriesCoordinator) IsCompleted(ctx context.Context, seriesID string) (bool, error) {
	exists, err := c.redis.Exists(ctx, constants.RedisCardObservationSeriesCompletedKey(seriesID)).Result()
	if err != nil {
		return false, errors.Wrap(errors.CodeInternalError, err, "读取卡观测序列完成状态失败")
	}
	return exists > 0, nil
}

// ClaimAttempt 以 series_id + attempt 原子保证任务只执行一次。
func (c *SeriesCoordinator) ClaimAttempt(ctx context.Context, seriesID string, attempt int) (bool, error) {
	claimed, err := c.redis.SetNX(ctx, constants.RedisCardObservationAttemptKey(seriesID, attempt), "processing", constants.CardObservationSeriesResultTTL).Result()
	if err != nil {
		return false, errors.Wrap(errors.CodeInternalError, err, "领取卡观测序列尝试失败")
	}
	return claimed, nil
}

// AcquireRequest 获取仅覆盖实际 Gateway 请求的互斥，并返回最小间隔剩余等待时间。
func (c *SeriesCoordinator) AcquireRequest(ctx context.Context, payload cardapp.SeriesTaskPayload, provider string) (func(), time.Duration, bool, error) {
	// 流量观测复用现有卡级同步锁，避免事件、轮询和手动刷新并发读取同一上游读数。
	if payload.SyncType == constants.CardObservationSyncTypeTraffic {
		if cardID, parseErr := strconv.ParseUint(payload.ResourceID, 10, 64); parseErr == nil && cardID > 0 {
			return c.acquireTrafficRequest(ctx, payload, provider, uint(cardID))
		}
	}
	token := uuid.NewString()
	lockKey := constants.RedisCardObservationInflightKey(provider, payload.SyncType, payload.ResourceID)
	lastKey := constants.RedisCardObservationLastRequestKey(provider, payload.SyncType, payload.ResourceID)
	nowMS := c.now().UTC().UnixMilli()
	result, err := c.redis.Eval(ctx, `
if redis.call('EXISTS', KEYS[1]) == 1 then return {-1} end
local now = tonumber(ARGV[2])
local minimum = tonumber(ARGV[3])
local last = tonumber(redis.call('GET', KEYS[2]) or '0')
local wait = math.max(0, last + minimum - now)
redis.call('PSETEX', KEYS[1], ARGV[4], ARGV[1])
redis.call('PSETEX', KEYS[2], minimum * 3, now + wait)
return {wait}
`, []string{lockKey, lastKey}, token, nowMS, constants.CardObservationGatewayMinInterval.Milliseconds(), constants.CardObservationGatewayLockTTL.Milliseconds()).Int64Slice()
	if err != nil || len(result) != 1 {
		return nil, 0, false, errors.Wrap(errors.CodeInternalError, err, "获取卡观测 Gateway 请求互斥失败")
	}
	if result[0] < 0 {
		return func() {}, 0, false, nil
	}
	release := func() {
		_, _ = c.redis.Eval(context.Background(), `
if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end
return 0
`, []string{lockKey}, token).Result()
	}
	return release, time.Duration(result[0]) * time.Millisecond, true, nil
}

func (c *SeriesCoordinator) acquireTrafficRequest(ctx context.Context, payload cardapp.SeriesTaskPayload, provider string, cardID uint) (func(), time.Duration, bool, error) {
	token, acquired, err := c.trafficLock.Acquire(ctx, cardID)
	if err != nil {
		return nil, 0, false, errors.Wrap(errors.CodeInternalError, err, "获取卡流量 Gateway 请求互斥失败")
	}
	if !acquired {
		return func() {}, 0, false, nil
	}
	release := func() {
		_ = c.trafficLock.Release(context.Background(), cardID, token)
	}
	lastKey := constants.RedisCardObservationLastRequestKey(provider, payload.SyncType, payload.ResourceID)
	nowMS := c.now().UTC().UnixMilli()
	wait, err := c.redis.Eval(ctx, `
local now = tonumber(ARGV[1])
local minimum = tonumber(ARGV[2])
local last = tonumber(redis.call('GET', KEYS[1]) or '0')
local wait = math.max(0, last + minimum - now)
redis.call('PSETEX', KEYS[1], minimum * 3, now + wait)
return wait
`, []string{lastKey}, nowMS, constants.CardObservationGatewayMinInterval.Milliseconds()).Int64()
	if err != nil {
		release()
		return nil, 0, false, errors.Wrap(errors.CodeInternalError, err, "获取卡流量 Gateway 最小请求间隔失败")
	}
	return release, time.Duration(wait) * time.Millisecond, true, nil
}

// FinishAttempt 保存尝试终态，并在最后一次尝试后释放同场景活跃序列。
func (c *SeriesCoordinator) FinishAttempt(ctx context.Context, payload cardapp.SeriesTaskPayload) error {
	if err := c.redis.Set(ctx, constants.RedisCardObservationAttemptKey(payload.SeriesID, payload.Attempt), "completed", constants.CardObservationSeriesResultTTL).Err(); err != nil {
		return errors.Wrap(errors.CodeInternalError, err, "完成卡观测序列尝试失败")
	}
	if payload.Attempt < constants.CardObservationSeriesAttemptCount {
		return nil
	}
	return c.releaseActiveSeries(ctx, payload, false)
}

// CompleteSeries 提前完成当前及剩余任务，并释放同场景合并键。
func (c *SeriesCoordinator) CompleteSeries(ctx context.Context, payload cardapp.SeriesTaskPayload) error {
	if err := c.releaseActiveSeries(ctx, payload, true); err != nil {
		return err
	}
	pipe := c.redis.TxPipeline()
	for attempt := payload.Attempt; attempt <= constants.CardObservationSeriesAttemptCount; attempt++ {
		pipe.Set(ctx, constants.RedisCardObservationAttemptKey(payload.SeriesID, attempt), "completed", constants.CardObservationSeriesResultTTL)
	}
	if _, err := pipe.Exec(ctx); err != nil {
		return errors.Wrap(errors.CodeInternalError, err, "标记卡观测剩余尝试完成失败")
	}
	return nil
}

func (c *SeriesCoordinator) releaseActiveSeries(ctx context.Context, payload cardapp.SeriesTaskPayload, completed bool) error {
	seriesKey := constants.RedisCardObservationSeriesKey(payload.Scene, payload.ResourceType, payload.ResourceID, payload.SyncType)
	indexKey := constants.RedisCardObservationSeriesIndexKey(payload.ResourceType, payload.ResourceID, payload.SyncType)
	completedValue := "0"
	if completed {
		completedValue = "1"
	}
	if _, err := c.redis.Eval(ctx, `
if ARGV[2] == '1' then redis.call('SET', KEYS[3], '1', 'EX', ARGV[3]) end
if redis.call('HGET', KEYS[1], 'series_id') == ARGV[1] then
  redis.call('DEL', KEYS[1])
  redis.call('SREM', KEYS[2], KEYS[1])
end
return 1
`, []string{seriesKey, indexKey, constants.RedisCardObservationSeriesCompletedKey(payload.SeriesID)}, payload.SeriesID, completedValue, int(constants.CardObservationSeriesResultTTL.Seconds())).Result(); err != nil {
		return errors.Wrap(errors.CodeInternalError, err, "释放卡观测活跃序列失败")
	}
	return nil
}

// CompleteResourceSeries 提前完成同资源、同步类型下所有场景的活跃序列。
func (c *SeriesCoordinator) CompleteResourceSeries(ctx context.Context, resourceType, resourceID, syncType string) error {
	indexKey := constants.RedisCardObservationSeriesIndexKey(resourceType, resourceID, syncType)
	seriesKeys, err := c.redis.SMembers(ctx, indexKey).Result()
	if err != nil {
		return errors.Wrap(errors.CodeInternalError, err, "读取卡观测活跃序列索引失败")
	}
	for _, seriesKey := range seriesKeys {
		seriesID, getErr := c.redis.HGet(ctx, seriesKey, "series_id").Result()
		if getErr == redis.Nil {
			_ = c.redis.SRem(ctx, indexKey, seriesKey).Err()
			continue
		}
		if getErr != nil {
			return errors.Wrap(errors.CodeInternalError, getErr, "读取卡观测活跃序列失败")
		}
		pipe := c.redis.TxPipeline()
		pipe.Set(ctx, constants.RedisCardObservationSeriesCompletedKey(seriesID), "1", constants.CardObservationSeriesResultTTL)
		pipe.Del(ctx, seriesKey)
		pipe.SRem(ctx, indexKey, seriesKey)
		if _, pipeErr := pipe.Exec(ctx); pipeErr != nil {
			return errors.Wrap(errors.CodeInternalError, pipeErr, "提前完成卡观测资源序列失败")
		}
	}
	return nil
}

var _ cardapp.SeriesCoordinator = (*SeriesCoordinator)(nil)

func formatCardID(cardID uint) string {
	return strconv.FormatUint(uint64(cardID), 10)
}
