package repository

import (
	"context"
	"errors"
	"fmt"
	"time"

	redislib "gateway/internal/library/redis"
	authdomain "gateway/internal/model/auth/domain"
	domrepo "gateway/internal/model/auth/domain/repository"

	"github.com/zeromicro/go-zero/core/stores/redis"
)

type redisTokenRevokeStore struct {
	client *redis.Redis
}

// NewRedisTokenRevokeStore creates a Redis-backed JWT revoke store.
func NewRedisTokenRevokeStore(client *redislib.Client) domrepo.TokenRevokeStore {
	if client == nil || client.Zero() == nil {
		panic("auth: redis client is required for token revoke store")
	}
	return &redisTokenRevokeStore{client: client.Zero()}
}

func (s *redisTokenRevokeStore) SavePair(ctx context.Context, accessJTI, refreshJTI string, accessTTL, refreshTTL time.Duration) error {
	if accessJTI == "" || refreshJTI == "" {
		return fmt.Errorf("auth: jwt pair jti is required")
	}
	accessSec := ttlSeconds(accessTTL)
	refreshSec := ttlSeconds(refreshTTL)
	if err := s.client.SetexCtx(ctx, authdomain.JWTPairRedisKey(accessJTI), refreshJTI, accessSec); err != nil {
		return err
	}
	return s.client.SetexCtx(ctx, authdomain.JWTPairRedisKey(refreshJTI), accessJTI, refreshSec)
}

func (s *redisTokenRevokeStore) GetPairedJTI(ctx context.Context, jti string) (string, error) {
	if jti == "" {
		return "", fmt.Errorf("auth: jti is required")
	}
	val, err := s.client.GetCtx(ctx, authdomain.JWTPairRedisKey(jti))
	if errors.Is(err, redis.Nil) || val == "" {
		return "", nil
	}
	if err != nil {
		return "", err
	}
	return val, nil
}

func (s *redisTokenRevokeStore) DeletePair(ctx context.Context, accessJTI, refreshJTI string) error {
	keys := make([]string, 0, 2)
	if accessJTI != "" {
		keys = append(keys, authdomain.JWTPairRedisKey(accessJTI))
	}
	if refreshJTI != "" {
		keys = append(keys, authdomain.JWTPairRedisKey(refreshJTI))
	}
	if len(keys) == 0 {
		return nil
	}
	_, err := s.client.DelCtx(ctx, keys...)
	return err
}

func (s *redisTokenRevokeStore) Blacklist(ctx context.Context, jti string, ttl time.Duration) error {
	if jti == "" {
		return fmt.Errorf("auth: jti is required")
	}
	return s.client.SetexCtx(ctx, authdomain.JWTBlacklistRedisKey(jti), "1", ttlSeconds(ttl))
}

func (s *redisTokenRevokeStore) IsBlacklisted(ctx context.Context, jti string) (bool, error) {
	if jti == "" {
		return false, fmt.Errorf("auth: jti is required")
	}
	exists, err := s.client.ExistsCtx(ctx, authdomain.JWTBlacklistRedisKey(jti))
	if err != nil {
		return false, err
	}
	return exists, nil
}

func ttlSeconds(d time.Duration) int {
	sec := int(d.Round(time.Second).Seconds())
	if sec < 1 {
		return 1
	}
	return sec
}

var _ domrepo.TokenRevokeStore = (*redisTokenRevokeStore)(nil)
