package service

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/echochat/backend/config"
	"github.com/echochat/backend/pkg/logs"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

// Redis Key 前缀，按 client_type 隔离前台和管理端的 Token
// 格式：echo:auth:token:{client_type}:{user_id}
// 例如：echo:auth:token:frontend:1 / echo:auth:token:admin:1
const (
	keyPrefixAccessToken  = "echo:auth:token:"   // echo:auth:token:{client_type}:{user_id}
	keyPrefixRefreshToken = "echo:auth:refresh:"  // echo:auth:refresh:{client_type}:{user_id}

	// cloudLawUsernamePrefix 云律临时会议账号用户名前缀（与 meeting.cloudLawUsername 保持一致）。
	// 这类账号按手机号即时创建、仅用于视频/语音会议，会在「双方进会 / SSO 多跳 / 多端」时被重复签发，
	// 单点登录语义会把先签发的 token 挤掉。对其放宽校验，避免 web-view 进会误报「认证已失效」。
	cloudLawUsernamePrefix = "cloudlaw_"
)

// isCloudLawUsername 判断是否为云律临时会议账号
func isCloudLawUsername(username string) bool {
	return strings.HasPrefix(username, cloudLawUsernamePrefix)
}

// TokenStore 管理 Token 在 Redis 中的存取
// 实现有状态 JWT：登录存入、验证时校验、登出时删除
type TokenStore struct {
	redis  *redis.Client
	jwtCfg *config.JWTConfig
}

// NewTokenStore 创建 TokenStore 实例
func NewTokenStore(redisClient *redis.Client, jwtCfg *config.JWTConfig) *TokenStore {
	return &TokenStore{
		redis:  redisClient,
		jwtCfg: jwtCfg,
	}
}

// SaveTokens 将 Access Token 和 Refresh Token 存入 Redis
// 每次登录/注册时调用，覆盖同一 clientType 下的旧 Token
// clientType 用于隔离前台（frontend）和管理端（admin）的 Token
func (s *TokenStore) SaveTokens(ctx context.Context, userID int64, clientType, accessToken, refreshToken string) error {
	funcName := "service.token_store.SaveTokens"

	accessKey := fmt.Sprintf("%s%s:%d", keyPrefixAccessToken, clientType, userID)
	refreshKey := fmt.Sprintf("%s%s:%d", keyPrefixRefreshToken, clientType, userID)

	accessTTL := time.Duration(s.jwtCfg.AccessExpireMin) * time.Minute
	refreshTTL := time.Duration(s.jwtCfg.RefreshExpireDay) * 24 * time.Hour

	pipe := s.redis.Pipeline()
	pipe.Set(ctx, accessKey, accessToken, accessTTL)
	pipe.Set(ctx, refreshKey, refreshToken, refreshTTL)

	if _, err := pipe.Exec(ctx); err != nil {
		logs.Error(ctx, funcName, "保存 Token 到 Redis 失败",
			zap.Int64("user_id", userID),
			zap.String("client_type", clientType),
			zap.Error(err),
		)
		return err
	}

	logs.Debug(ctx, funcName, "Token 已存入 Redis",
		zap.Int64("user_id", userID),
		zap.String("client_type", clientType),
		zap.Duration("access_ttl", accessTTL),
		zap.Duration("refresh_ttl", refreshTTL),
	)
	return nil
}

// ValidateAccessToken 校验 Access Token 是否与 Redis 中存储的一致
// clientType 用于定位正确的 Redis key（前台 vs 管理端）
// username 用于识别云律临时会议账号，对其放宽有状态校验
//
// 云律临时会议账号(cloudlaw_*)说明：按手机号即时创建、默认口令、仅用于视频/语音会议、角色受限，
// 在「双方进会 / SSO 多跳 / 多端 / 重进会议」时会被重复签发，单点登录语义会把先签发的 token 挤掉；
// 叠加 Redis key 过期/内存淘汰/重启、登出清理、并发等情况，Redis 侧可能出现「不一致 / 缺失 / 查询失败」。
// 由于调用方(中间件/WS 握手)在调用本方法前已用 ParseToken 校验过 JWT 的签名与有效期，
// 对这类临时账号只要 JWT 有效即放行（不强依赖 Redis 有状态记录），避免 web-view 进会偶发「认证已失效」。
// 普通账号(前台用户/管理端)仍维持严格的有状态校验。
func (s *TokenStore) ValidateAccessToken(ctx context.Context, userID int64, clientType, token, username string) bool {
	funcName := "service.token_store.ValidateAccessToken"

	cloudLaw := isCloudLawUsername(username)

	key := fmt.Sprintf("%s%s:%d", keyPrefixAccessToken, clientType, userID)
	stored, err := s.redis.Get(ctx, key).Result()
	if err == redis.Nil {
		// 云律临时账号：Redis 无记录（挤号后过期 / 内存淘汰 / 重启 / 登出清理）也凭有效 JWT 放行
		if cloudLaw {
			logs.Debug(ctx, funcName, "云律临时账号 Redis 无 token 记录，按宽松策略放行（仅凭有效 JWT）",
				zap.Int64("user_id", userID),
				zap.String("client_type", clientType),
			)
			return true
		}
		logs.Debug(ctx, funcName, "Token 不存在（已登出或过期）",
			zap.Int64("user_id", userID),
			zap.String("client_type", clientType),
		)
		return false
	}
	if err != nil {
		// 云律临时账号：Redis 抖动/不可用时凭有效 JWT 放行，避免阻断进会
		if cloudLaw {
			logs.Warn(ctx, funcName, "Redis 查询 token 失败，云律临时账号按宽松策略放行（仅凭有效 JWT）",
				zap.Int64("user_id", userID),
				zap.String("client_type", clientType),
				zap.Error(err),
			)
			return true
		}
		logs.Error(ctx, funcName, "Redis 查询 Token 失败",
			zap.Int64("user_id", userID),
			zap.String("client_type", clientType),
			zap.Error(err),
		)
		return false
	}

	if stored == token {
		return true
	}

	// 云律临时会议账号：token 已被新会话挤号（stored != token）也放行
	if cloudLaw {
		logs.Debug(ctx, funcName, "云律临时账号 token 已被新会话覆盖，按宽松策略放行",
			zap.Int64("user_id", userID),
			zap.String("client_type", clientType),
		)
		return true
	}

	return false
}

// ValidateRefreshToken 校验 Refresh Token 是否与 Redis 中存储的一致
func (s *TokenStore) ValidateRefreshToken(ctx context.Context, userID int64, clientType, token string) bool {
	key := fmt.Sprintf("%s%s:%d", keyPrefixRefreshToken, clientType, userID)
	stored, err := s.redis.Get(ctx, key).Result()
	if err != nil {
		return false
	}
	return stored == token
}

// RemoveTokens 从 Redis 删除指定 clientType 下用户的 Token（登出时调用）
func (s *TokenStore) RemoveTokens(ctx context.Context, userID int64, clientType string) error {
	funcName := "service.token_store.RemoveTokens"

	accessKey := fmt.Sprintf("%s%s:%d", keyPrefixAccessToken, clientType, userID)
	refreshKey := fmt.Sprintf("%s%s:%d", keyPrefixRefreshToken, clientType, userID)

	if err := s.redis.Del(ctx, accessKey, refreshKey).Err(); err != nil {
		logs.Error(ctx, funcName, "删除 Token 失败",
			zap.Int64("user_id", userID),
			zap.String("client_type", clientType),
			zap.Error(err),
		)
		return err
	}

	logs.Info(ctx, funcName, "Token 已从 Redis 删除",
		zap.Int64("user_id", userID),
		zap.String("client_type", clientType),
	)
	return nil
}
