package event_tracker //nolint:staticcheck

import (
	"fmt"
	"sync"
	"sync/atomic"
)

const (
	ModuleName   = "event_tracker"
	maxEventSize = 1000
)

type Event interface {
	// DeduplicateKey returns a unique key for the event to deduplicate it
	DeduplicateKey() string
}

type Events struct {
	mu                sync.Mutex
	events            []Event
	accumulatedEvents *atomic.Int64
	eventSizeLimit    chan struct{}
	deduplicateMap    map[string]struct{}
}

type EventsInterface interface {
	EmitEvent(event Event)
}

type EventTracker struct {
	eventLists        map[string]*Events
	accumulatedEvents atomic.Int64
	eventSizeLimit    chan struct{}
}

func NewEventTracker() *EventTracker {
	return &EventTracker{
		eventLists:     map[string]*Events{},
		eventSizeLimit: make(chan struct{}),
	}
}

type EventTrackerRegisterer interface {
	RegisterEvent(name string) EventsInterface
}

func (et *EventTracker) RegisterEvent(name string) EventsInterface {
	if _, exists := et.eventLists[name]; exists {
		panic(fmt.Errorf("event with name %s already exists", name))
	}
	e := &Events{
		accumulatedEvents: &et.accumulatedEvents,
		eventSizeLimit:    et.eventSizeLimit,
		deduplicateMap:    make(map[string]struct{}),
	}
	et.eventLists[name] = e
	return e
}

func (e *Events) EmitEvent(event Event) {
	e.mu.Lock()
	defer e.mu.Unlock()

	// Deduplicate the event based on the deduplicate key.
	deduplicateKey := event.DeduplicateKey()
	if _, exists := e.deduplicateMap[deduplicateKey]; exists {
		return
	}
	e.deduplicateMap[deduplicateKey] = struct{}{}

	e.events = append(e.events, event)
	e.accumulatedEvents.Add(int64(1))

	// If the number of accumulated events is greater than the maxEventSize,
	// send a signal to the eventSizeLimit channel to flush the events.
	if e.accumulatedEvents.Load() > maxEventSize {
		select {
		case e.eventSizeLimit <- struct{}{}:
		default:
			// Discard the message if no one is listening
		}
	}
}

func (e *Events) Subtract(v []Event) {
	e.mu.Lock()
	e.events = append(e.events[:0], e.events[len(v):]...)
	e.mu.Unlock()
	e.accumulatedEvents.Add(-int64(len(v)))
}

type EventTrackerCollector interface {
	CloneEventData() *EventData
	Subtract(*EventData)
	AccumulatedEvents() int64
	GetEventSizeLimit() <-chan struct{}
}

func (et *EventTracker) CloneEventData() *EventData {
	events := make(map[string][]Event)
	for k, v := range et.eventLists {
		v.mu.Lock()
		clear(v.deduplicateMap)
		if len(v.events) == 0 {
			v.mu.Unlock()
			continue
		}
		newEvents := make([]Event, 0, len(v.events))
		newEvents = append(newEvents, v.events...)
		events[k] = newEvents
		v.mu.Unlock()
	}
	return &EventData{
		Events: events,
	}
}

func (et *EventTracker) Subtract(ed *EventData) {
	for k, v := range ed.Events {
		e := et.eventLists[k]
		e.Subtract(v)
	}
}

func (et *EventTracker) AccumulatedEvents() int64 {
	return et.accumulatedEvents.Load()
}

func (et *EventTracker) GetEventSizeLimit() <-chan struct{} {
	return et.eventSizeLimit
}

// EventData is a struct that contains all the eventLists and used to send them to GitLab
type EventData struct {
	Events map[string][]Event
}

func (ed *EventData) IsEmpty() bool {
	return len(ed.Events) == 0
}

type EventTrackerInterface interface {
	EventTrackerRegisterer
	EventTrackerCollector
}
