package redis

import (
	"fmt"

	red "github.com/redis/go-redis/v9"
	"github.com/zeromicro/go-zero/core/logx"
)

// ClosableNode interface represents a closable redis node.
type ClosableNode interface {
	RedisNode
	Close()
}

// CreateBlockingNode creates a dedicated RedisNode for blocking operations.
//
// Blocking Redis commands (like BLPOP, BRPOP, XREADGROUP with block parameter) hold connections
// for extended periods while waiting for data. Using them with the regular Redis connection pool
// can exhaust all available connections, causing other operations to fail or timeout.
//
// CreateBlockingNode creates a separate Redis client with a minimal connection pool (size 1) that
// is dedicated to blocking operations. This ensures blocking commands don't interfere with regular
// Redis operations.
//
// Example usage:
//
//	rds := redis.MustNewRedis(redis.RedisConf{
//	    Host: "localhost:6379",
//	    Type: redis.NodeType,
//	})
//
//	// Create a dedicated node for blocking operations
//	node, err := redis.CreateBlockingNode(rds)
//	if err != nil {
//	    // handle error
//	}
//	defer node.Close() // Important: close the node when done
//
//	// Use the node for blocking operations
//	value, err := rds.Blpop(node, "mylist")
//	if err != nil {
//	    // handle error
//	}
//
// The returned ClosableNode must be closed when no longer needed to release resources.
func CreateBlockingNode(r *Redis) (ClosableNode, error) {
	timeout := readWriteTimeout + blockingQueryTimeout

	switch r.Type {
	case NodeType:
		client := red.NewClient(&red.Options{
			Addr:                     r.Addr,
			Username:                 r.User,
			Password:                 r.Pass,
			DB:                       defaultDatabase,
			MaxRetries:               maxRetries,
			PoolSize:                 1,
			MinIdleConns:             1,
			ReadTimeout:              timeout,
			Protocol:                 r.protocol,
			DisableIdentity:          r.identity,
			MaintNotificationsConfig: r.maintNotificationsConfig(),
		})
		return &clientBridge{client}, nil
	case ClusterType:
		client := red.NewClusterClient(&red.ClusterOptions{
			Addrs:                    splitClusterAddrs(r.Addr),
			Username:                 r.User,
			Password:                 r.Pass,
			MaxRetries:               maxRetries,
			PoolSize:                 1,
			MinIdleConns:             1,
			ReadTimeout:              timeout,
			Protocol:                 r.protocol,
			DisableIdentity:          r.identity,
			MaintNotificationsConfig: r.maintNotificationsConfig(),
		})
		return &clusterBridge{client}, nil
	default:
		return nil, fmt.Errorf("unknown redis type: %s", r.Type)
	}
}

type (
	clientBridge struct {
		*red.Client
	}

	clusterBridge struct {
		*red.ClusterClient
	}
)

func (bridge *clientBridge) Close() {
	if err := bridge.Client.Close(); err != nil {
		logx.Errorf("Error occurred on close redis client: %s", err)
	}
}

func (bridge *clusterBridge) Close() {
	if err := bridge.ClusterClient.Close(); err != nil {
		logx.Errorf("Error occurred on close redis cluster: %s", err)
	}
}
