package tieventbus

import (
	"database/sql"
	"time"

	"github.com/Shopify/sarama"
	"github.com/prometheus/client_golang/prometheus"
)

type SaramaConfigModifier func(conf *sarama.Config)

type EventbusConfig struct {
	// Your app-name
	// It affects the isolation of consuming groupKey between different App
	AppName string

	DB *sql.DB // If DB is not set, Eventbus.PublishWithDBTx is unavailable

	// This table store the message by Publisher.PublishWithDBTransaction
	// Eventbus will send message from here to MQ in background.
	// default value: eventbus_tx_pub_table
	TxPubTable string

	// Global MQ Request timeout .Default: 1s
	// This value will affect Consumer/Publisher and connect to mq
	MQTimeout time.Duration

	SaramaConfigModifier4Subscriber SaramaConfigModifier
	SaramaConfigModifier4Producer   SaramaConfigModifier
}

type Event struct {
	ID string

	Type string // same type will be sent to same MQ topic

	// the userData will store here,
	// when you set or reflect this value, you must use pointer of your struct
	// otherwise, eventbus could not process it
	// You can use by flowing
	// reflect:
	// s := event.Data.(*YourStruct)
	// set:
	// event.Data = &YourStruct{}
	Data interface{}
}

type HandleFunc func(event *Event) error

type HandlerConfig struct {
	EventType       string     // the eventTypeKey you want to subscribe to
	Group           string     // the consumer groupKey, one groupKey only process event once, and diff groups can consume one event repeatedly
	NumOfGoroutines uint       // default 1
	RetryTimes      uint       // default 0, disable retry
	HandleFunc      HandleFunc // Function called when event is received

	// is the instance of data structure of data.
	// Eventbus uses it by uses it by new a pointer of your struct,
	// usage: handleConfig.DataStruct = YourStruct{} or handleConfig.DataStruct = &YourStruct{}
	// support proto.Message
	DataStruct interface{}
}

type AsyncResult struct {
	// Finished is closed when calling is finished.
	Finished chan struct{}
	Error    error
}

type Eventbus interface {
	// RegisterHandler registers the handleFunc
	// for process the new message from the MQ
	// It will be active after the Eventbus.Run has been called
	RegisterHandler(config HandlerConfig)

	// Publish publishes provided messages to given topic directly.
	Publish(event *Event) error

	// PublishAsync is the wrapper of Publish, but it not blocks the running thread
	// You can call <-AsyncResult.Finished to waiting it finished
	// If error occurred, the error will be set at AsyncResult.Error
	PublishAsync(event *Event) *AsyncResult

	// PublishWithDBTx Store message to db with transaction
	// and the Message will be sent to MQ in the background
	PublishWithDBTx(tx *sql.Tx, event *Event) error

	// Run will start all subscribers
	// and the publisher for send message in the background
	Run() error

	// RunUtilClosed is same as Run, but it will block until eventbus closed
	RunUtilClosed() error

	// Running is closed when eventbus is running.
	// In other words: you can wait till eventbus is running using
	//		fmt.Println("Starting router")
	//		go eventbus.RunUtilClosed()
	//		<- eventbus.Running()
	//		fmt.Println("Eventbus is running")
	// Notice: You should not call eventbus.Running() before call eventbus.RunUtilClosed
	Running() chan struct{}

	// FixDLQ will only activate dlq subscriber
	// and will exit when no message is entered for more than `timeout` seconds
	FixDLQ(timeout time.Duration) error

	EnableMetrics(prometheusRegistry prometheus.Registerer, namespace string, subsystem string)

	Close()
}
