package main
import (
	"bytes"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"sort"
	"strings"
	"sync"
	"time"
	"html/template"
	"github.com/localhots/koff"
)
type clusterState struct {
	consumerOffsets map[string]koff.OffsetMessage
	consumerGroups  map[string]koff.GroupMessage
}
var state = &clusterState{
	consumerOffsets: make(map[string]koff.OffsetMessage),
	consumerGroups:  make(map[string]koff.GroupMessage),
}
var lock sync.Mutex
func main() {
	brokers := flag.String("brokers", "", "Comma separated list of brokers")
	flag.Parse()
	if *brokers == "" {
		fmt.Println("Brokers list required")
		flag.Usage()
		os.Exit(1)
	}
	c, err := koff.NewConsumer(strings.Split(*brokers, ","), false)
	if err != nil {
		log.Fatalf("Failed to create consumer: %v", err)
	}
	defer c.Close()
	go func() {
		for msg := range c.Messages() {
			state.add(msg)
		}
	}()
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write(state.render())
	})
	http.ListenAndServe(":8080", nil)
}
func (s *clusterState) add(msg koff.Message) {
	lock.Lock()
	defer lock.Unlock()
	if msg.OffsetMessage != nil {
		cur, ok := s.consumerOffsets[msg.Consumer]
		if !ok || cur.CommittedAt.Before(msg.OffsetMessage.CommittedAt) {
			s.consumerOffsets[msg.Consumer] = *msg.OffsetMessage
		}
	} else {
		cur, ok := s.consumerGroups[msg.Consumer]
		if !ok || (msg.GroupMessage.GenerationID > cur.GenerationID && msg.GroupMessage.Complete()) {
			s.consumerGroups[msg.Consumer] = *msg.GroupMessage
		}
	}
}
//
// Render
//
var htmlTpl = template.Must(template.New("main").Parse(`
Kafka Consumers
Consumer Offsets
	| Consumer | 
	Topic | 
	Partition | 
	Offset | 
	Timestamp | 
{{range .ConsumerOffsets}}
	| {{.Consumer}} | 
	{{.Topic}} | 
	{{.Partition}} | 
	{{.Offset}} | 
	{{.Timestamp}} | 
{{end}}
Consumer Groups
{{range .ConsumerGroups}}
	| {{.Consumer}} | 
	| Consumer | 
	Topic | 
	Partition | 
	Leader | 
{{range .Members}}
{{$id := .ID}}
{{range .Assignment}}
	| {{$id}} | 
	{{.Topic}} | 
	{{.Partition}} | 
	Yes | 
{{end}}
{{end}}
{{end}}
`))
type offsetMessage struct {
	Consumer  string
	Timestamp string
	koff.OffsetMessage
}
type groupMessage struct {
	Consumer string
	koff.GroupMessage
}
func (s *clusterState) render() []byte {
	var tpl struct {
		ConsumerOffsets []offsetMessage
		ConsumerGroups  []groupMessage
	}
	lock.Lock()
	defer lock.Unlock()
	for k, m := range s.consumerOffsets {
		if strings.HasPrefix(k, "console-consumer") {
			continue
		}
		tpl.ConsumerOffsets = append(tpl.ConsumerOffsets, offsetMessage{
			Consumer:      k,
			OffsetMessage: m,
			Timestamp:     m.CommittedAt.Format(time.Stamp),
		})
	}
	sort.Slice(tpl.ConsumerOffsets, func(i, j int) bool {
		return tpl.ConsumerOffsets[i].Consumer < tpl.ConsumerOffsets[j].Consumer
	})
	for k, m := range s.consumerGroups {
		// if strings.HasPrefix(k, "console-consumer") {
		// 	continue
		// }
		tpl.ConsumerGroups = append(tpl.ConsumerGroups, groupMessage{
			Consumer:     k,
			GroupMessage: m,
		})
	}
	sort.Slice(tpl.ConsumerGroups, func(i, j int) bool {
		return tpl.ConsumerGroups[i].Consumer < tpl.ConsumerGroups[j].Consumer
	})
	var buf bytes.Buffer
	err := htmlTpl.Execute(&buf, tpl)
	if err != nil {
		panic(err)
	}
	return buf.Bytes()
}