package redisUtil

import (
	"context"
	"fmt"
	"time"

	"github.com/gomodule/redigo/redis"
)

//  订阅回调函数
type SubscribeCallback func() error

// Subscriber
//  @description: 订阅者
type Subscriber struct {
	// pool 订阅者连接池
	pool *RedisPool
	// callBack 订阅者回调函数
	callBack SubscribeCallback
}

// NewSubscriber
//  @description: 构建一个订阅者
// parameter:
//  @pool:
//  @callBack:
// return:
//  @*Subscriber:
func NewSubscriber(pool *RedisPool, callBack SubscribeCallback) *Subscriber {
	return &Subscriber{pool: pool, callBack: callBack}
}

// Promulgator
//  @description: 发布者
type Promulgator struct {
	// pool 发布者连接池
	pool *RedisPool
}

// NewPromulgator
//  @description: 构建一个发布者
// parameter:
//  @pool:
// return:
//  @*Promulgator:
func NewPromulgator(pool *RedisPool) *Promulgator {
	return &Promulgator{pool: pool}
}

// Publish
//  @description: 发布消息
// parameter:
//  @receiver s:
//  @channel:
//  @message:
// return:
//  @error:
func (s *Promulgator) Publish(channel, message string) error {
	c := s.pool.GetConnection()
	defer c.Close()

	_, err := c.Do("PUBLISH", channel, message)
	if err != nil {
		return fmt.Errorf("redis publish %s %s, err: %v", channel, message, err)
	}
	//n, err := s.pool.Int(result)
	//if err != nil {
	//	return fmt.Errorf("redis publish %s %s, err: %v", channel, message, err)
	//}

	return nil
}

// Subscribe
//  @description: 订阅者订阅消息
// parameter:
//  @receiver s:
//  @ctx:
//  @channel: 频道
// return:
//  @error:
func (s *Subscriber) Subscribe(ctx context.Context, channel ...string) error {
	sub := redis.PubSubConn{Conn: s.pool.GetConnection()}
	if err := sub.Subscribe(redis.Args{}.AddFlat(channel)...); err != nil {
		return err
	}
	done := make(chan error, 1)

	// 启动一个新协程去持续订阅消息
	go func() {
		defer sub.Close()
		for {
			switch msg := sub.Receive().(type) {
			case error:
				done <- fmt.Errorf("redis pubsub receive err: %v", msg)
				return
			case redis.Message:
				if err := s.callBack(); err != nil {
					done <- err
					return
				}
			case redis.Subscription:
				if msg.Count == 0 {
					// 所有的订阅者都退出
					done <- nil
					return
				}
			}
		}
	}()

	// health check
	tick := time.NewTicker(time.Minute)
	defer tick.Stop()
	for {
		select {
		case <-ctx.Done():
			if err := sub.Unsubscribe(); err != nil {
				return fmt.Errorf("redis pubsub unsubscribe err: %v", err)
			}
			return nil
		case err := <-done:
			return err
		case <-tick.C:
			if err := sub.Ping(""); err != nil {
				return err
			}
		}
	}
}
