package server

import (
	"context"
	"fmt"
	"time"

	"github.com/redis/rueidis"
	"google.golang.org/protobuf/proto"
)

// AuthStore handles Redis operations for authentication data
type AuthStore interface {
	// OAuth state operations

	StoreOAuthState(ctx context.Context, stateValue string, state *OAuthState) error
	GetOAuthState(ctx context.Context, stateValue string) (*OAuthState, error)
	DeleteOAuthState(ctx context.Context, stateValue string) error

	// Transfer token operations

	StoreTransferToken(ctx context.Context, token string, tt *TransferToken) error
	GetTransferToken(ctx context.Context, token string) (*TransferToken, error)
	DeleteTransferToken(ctx context.Context, token string) error

	// User session operations

	StoreUserSession(ctx context.Context, sessionID string, us *UserSession) error
	GetUserSession(ctx context.Context, sessionID string) (*UserSession, error)
	DeleteUserSession(ctx context.Context, token string) error
}

// RedisAuthStore implements AuthStore using Redis
type RedisAuthStore struct {
	client           rueidis.Client
	keyPrefix        string
	oauthStateTTL    time.Duration
	userSessionTTL   time.Duration
	transferTokenTTL time.Duration
}

// NewRedisAuthStore creates a new Redis-based auth store
func NewRedisAuthStore(client rueidis.Client, keyPrefix string, oauthStateStateTTL, userSessionTTL, transferTokenTTL time.Duration) *RedisAuthStore {
	return &RedisAuthStore{
		client:           client,
		keyPrefix:        keyPrefix,
		oauthStateTTL:    oauthStateStateTTL,
		userSessionTTL:   userSessionTTL,
		transferTokenTTL: transferTokenTTL,
	}
}

// Key builders

func (r *RedisAuthStore) oauthStateKey(stateValue string) string {
	return fmt.Sprintf("%s:oauth_states:%s", r.keyPrefix, stateValue)
}

func (r *RedisAuthStore) transferTokenKey(token string) string {
	return fmt.Sprintf("%s:transfer_tokens:%s", r.keyPrefix, token)
}

func (r *RedisAuthStore) userSessionKey(sessionID string) string {
	return fmt.Sprintf("%s:user_sessions:%s", r.keyPrefix, sessionID)
}

// Generic Redis operations

func (r *RedisAuthStore) setProtoWithTTL(ctx context.Context, key string, data proto.Message, ttl time.Duration) error {
	protoData, err := proto.Marshal(data)
	if err != nil {
		return fmt.Errorf("failed to marshal protobuf: %w", err)
	}

	cmd := r.client.B().Set().Key(key).Value(rueidis.BinaryString(protoData)).Px(ttl).Build()
	return r.client.Do(ctx, cmd).Error()
}

func (r *RedisAuthStore) getProto(ctx context.Context, key string, target proto.Message) error {
	cmd := r.client.B().Get().Key(key).Build()
	result := r.client.Do(ctx, cmd)
	if err := result.Error(); err != nil {
		return err
	}

	data, err := result.AsBytes()
	if err != nil {
		return err
	}

	return proto.Unmarshal(data, target)
}

func (r *RedisAuthStore) deleteKey(ctx context.Context, key string) error {
	cmd := r.client.B().Del().Key(key).Build()
	return r.client.Do(ctx, cmd).Error()
}

// OAuth state operations

func (r *RedisAuthStore) StoreOAuthState(ctx context.Context, stateValue string, state *OAuthState) error {
	key := r.oauthStateKey(stateValue)
	return r.setProtoWithTTL(ctx, key, state, r.oauthStateTTL)
}

func (r *RedisAuthStore) GetOAuthState(ctx context.Context, stateValue string) (*OAuthState, error) {
	key := r.oauthStateKey(stateValue)
	state := &OAuthState{}
	err := r.getProto(ctx, key, state)
	if err != nil {
		if rueidis.IsRedisNil(err) {
			return nil, nil
		}
		return nil, err
	}
	return state, nil
}

func (r *RedisAuthStore) DeleteOAuthState(ctx context.Context, stateValue string) error {
	key := r.oauthStateKey(stateValue)
	return r.deleteKey(ctx, key)
}

// Transfer token operations

func (r *RedisAuthStore) StoreTransferToken(ctx context.Context, token string, tt *TransferToken) error {
	key := r.transferTokenKey(token)
	return r.setProtoWithTTL(ctx, key, tt, r.transferTokenTTL)
}

func (r *RedisAuthStore) GetTransferToken(ctx context.Context, token string) (*TransferToken, error) {
	key := r.transferTokenKey(token)
	tt := &TransferToken{}
	err := r.getProto(ctx, key, tt)
	if err != nil {
		if rueidis.IsRedisNil(err) {
			return nil, nil
		}
		return nil, err
	}
	return tt, nil
}

func (r *RedisAuthStore) DeleteTransferToken(ctx context.Context, token string) error {
	key := r.transferTokenKey(token)
	return r.deleteKey(ctx, key)
}

// User session operations

func (r *RedisAuthStore) StoreUserSession(ctx context.Context, sessionID string, us *UserSession) error {
	key := r.userSessionKey(sessionID)
	return r.setProtoWithTTL(ctx, key, us, r.userSessionTTL)
}

func (r *RedisAuthStore) GetUserSession(ctx context.Context, sessionID string) (*UserSession, error) {
	key := r.userSessionKey(sessionID)
	us := &UserSession{}
	err := r.getProto(ctx, key, us)
	if err != nil {
		if rueidis.IsRedisNil(err) {
			return nil, nil
		}
		return nil, err
	}
	return us, nil
}

func (r *RedisAuthStore) DeleteUserSession(ctx context.Context, token string) error {
	key := r.userSessionKey(token)
	return r.deleteKey(ctx, key)
}
