package session

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"time"

	"github.com/redis/go-redis/v9"
)

// Redis key 前缀前缀，避免与其他业务 key 冲突
const (
	keyPrefix     = "metalab:session:"
	userKeyPrefix = "metalab:user_sessions:"
)

// RedisStore 基于 Redis 的会话存储（生产环境推荐）
// 利用 Redis TTL 自动过期，无须后台清理 goroutine
type RedisStore struct {
	client          *redis.Client
	idleTimeout     time.Duration // 不记住我：空闲超时
	rememberTimeout time.Duration // 记住我：空闲超时
}

// NewRedisStore 创建 Redis 会话存储实例
func NewRedisStore(client *redis.Client, idleTimeout, rememberTimeout time.Duration) *RedisStore {
	return &RedisStore{
		client:          client,
		idleTimeout:     idleTimeout,
		rememberTimeout: rememberTimeout,
	}
}

// userKey 返回用户会话索引的 Redis key
func (rs *RedisStore) userKey(uid uint) string {
	return userKeyPrefix + fmt.Sprint(uid)
}

// sessionKeys 将 sid 列表转为完整的 Redis session key 列表
func (rs *RedisStore) sessionKeys(sids []string) []string {
	keys := make([]string, len(sids))
	for i, sid := range sids {
		keys[i] = keyPrefix + sid
	}
	return keys
}

// Get 根据会话 ID 获取会话（过期返回 nil）
func (rs *RedisStore) Get(id string) (*Session, error) {
	ctx := context.Background()
	data, err := rs.client.Get(ctx, keyPrefix+id).Bytes()
	if err == redis.Nil {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("Redis GET session: %w", err)
	}

	var s Session
	if err := json.Unmarshal(data, &s); err != nil {
		return nil, fmt.Errorf("Redis 反序列化 session: %w", err)
	}
	return &s, nil
}

// Set 写入/更新会话并设置 TTL
func (rs *RedisStore) Set(s *Session) error {
	ctx := context.Background()

	data, err := json.Marshal(s)
	if err != nil {
		return fmt.Errorf("Redis 序列化 session: %w", err)
	}

	ttl := rs.idleTimeout
	if s.RememberMe {
		ttl = rs.rememberTimeout
	}

	pipe := rs.client.Pipeline()

	// 1. 写入会话数据
	pipe.Set(ctx, keyPrefix+s.ID, data, ttl)

	// 2. 将 sid 加入用户会话索引（Redis SET）
	// 不设置 user_sessions 的 TTL——同一用户可能有不同 TTL 的会话，
	// 设置单一 TTL 会导致长生命周期会话的索引被提前过期
	pipe.SAdd(ctx, rs.userKey(s.UserID), s.ID)

	_, err = pipe.Exec(ctx)
	if err != nil {
		return fmt.Errorf("Redis SET session: %w", err)
	}
	return nil
}

// Delete 删除单个会话
func (rs *RedisStore) Delete(id string) error {
	ctx := context.Background()

	// 先读取 session 获取 UserID，再从集合中移除
	data, err := rs.client.Get(ctx, keyPrefix+id).Bytes()
	if err == redis.Nil {
		return nil
	}
	if err != nil {
		return fmt.Errorf("Redis GET session for delete: %w", err)
	}

	var s Session
	if err := json.Unmarshal(data, &s); err == nil {
		if sremErr := rs.client.SRem(ctx, rs.userKey(s.UserID), id).Err(); sremErr != nil {
			log.Printf("[RedisStore] Delete: SRem failed sid=%s uid=%d err=%v", id[:16]+"...", s.UserID, sremErr)
		}
	}

	return rs.client.Del(ctx, keyPrefix+id).Err()
}

// DeleteByUID 删除某用户的所有会话（改密/注销/强制下线）
func (rs *RedisStore) DeleteByUID(uid uint) error {
	ctx := context.Background()
	uidKey := rs.userKey(uid)

	sids, err := rs.client.SMembers(ctx, uidKey).Result()
	if err != nil {
		return fmt.Errorf("Redis SMEMBERS user_sessions: %w", err)
	}

	if len(sids) == 0 {
		return nil
	}

	pipe := rs.client.Pipeline()
	pipe.Del(ctx, rs.sessionKeys(sids)...)
	pipe.Del(ctx, uidKey)
	_, err = pipe.Exec(ctx)
	if err != nil {
		return fmt.Errorf("Redis DeleteByUID: %w", err)
	}

	log.Printf("[RedisStore] DeleteByUID uid=%d count=%d", uid, len(sids))
	return nil
}

// DeleteByUIDExclude 删除某用户的所有会话，但保留指定的会话 ID
func (rs *RedisStore) DeleteByUIDExclude(uid uint, excludeSID string) error {
	ctx := context.Background()
	uidKey := rs.userKey(uid)

	sids, err := rs.client.SMembers(ctx, uidKey).Result()
	if err != nil {
		return fmt.Errorf("Redis SMEMBERS user_sessions: %w", err)
	}

	var toDelete []string
	kept := 0
	for _, sid := range sids {
		if sid == excludeSID {
			kept++
			continue
		}
		toDelete = append(toDelete, sid)
	}

	if len(toDelete) == 0 {
		return nil
	}

	pipe := rs.client.Pipeline()
	pipe.Del(ctx, rs.sessionKeys(toDelete)...)
	for _, sid := range toDelete {
		pipe.SRem(ctx, uidKey, sid)
	}
	_, err = pipe.Exec(ctx)
	if err != nil {
		return fmt.Errorf("Redis DeleteByUIDExclude: %w", err)
	}

	log.Printf("[RedisStore] DeleteByUIDExclude uid=%d removed=%d kept=%d", uid, len(toDelete), kept)
	return nil
}

// ListByUID 列出某用户的所有活跃会话
func (rs *RedisStore) ListByUID(uid uint) ([]*Session, error) {
	ctx := context.Background()
	uidKey := rs.userKey(uid)

	sids, err := rs.client.SMembers(ctx, uidKey).Result()
	if err != nil {
		return nil, fmt.Errorf("Redis SMEMBERS user_sessions: %w", err)
	}

	if len(sids) == 0 {
		return nil, nil
	}

	results, err := rs.client.MGet(ctx, rs.sessionKeys(sids)...).Result()
	if err != nil {
		return nil, fmt.Errorf("Redis MGET sessions: %w", err)
	}

	var sessions []*Session
	for _, r := range results {
		if r == nil {
			continue
		}
		data, ok := r.(string)
		if !ok {
			continue
		}
		var s Session
		if err := json.Unmarshal([]byte(data), &s); err != nil {
			continue
		}
		sessions = append(sessions, &s)
	}
	return sessions, nil
}

// Cleanup Redis 自动 TTL 过期，无需手动清理
func (rs *RedisStore) Cleanup() int {
	return 0
}

// StartCleanup Redis 模式无需后台清理 goroutine
func (rs *RedisStore) StartCleanup(interval time.Duration) {
	// Redis TTL 自动过期，无需手动清理
}

// Metrics 返回 Redis 存储状态（活跃会话数统计代价高，返回 -1）
func (rs *RedisStore) Metrics() StoreMetrics {
	return StoreMetrics{
		Type:        "redis",
		Healthy:     true,
		ActiveCount: -1,
	}
}
