package helicopterfilerealtime

import (
	"errors"
	"log/slog"
	"strings"
	"sync"
)

const (
	DefaultEventType      = "helicopter_file_changed"
	DefaultSubscriberSize = 32
)

type Event struct {
	EventType         string `json:"event_type"`
	HelicopterID      string `json:"helicopter_id"`
	HelicopterFileID  string `json:"helicopter_file_id"`
	FileID            string `json:"file_id,omitempty"`
	Section           string `json:"section,omitempty"`
	Name              string `json:"name,omitempty"`
	MimeType          string `json:"mime_type,omitempty"`
	AttachmentPending bool   `json:"attachment_pending"`
	Position          int    `json:"position,omitempty"`
	IsMandatory       bool   `json:"is_mandatory,omitempty"`
	UpdatedAt         string `json:"updated_at,omitempty"`
}

type Subscription struct {
	events  <-chan Event
	done    <-chan struct{}
	closeFn func()
}

func (s *Subscription) Events() <-chan Event {
	if s == nil {
		return nil
	}
	return s.events
}

func (s *Subscription) Done() <-chan struct{} {
	if s == nil {
		return nil
	}
	return s.done
}

func (s *Subscription) Close() {
	if s == nil || s.closeFn == nil {
		return
	}
	s.closeFn()
}

type subscriber struct {
	events    chan Event
	done      chan struct{}
	match     func(Event) bool
	closeOnce sync.Once
}

func (s *subscriber) close() {
	if s == nil {
		return
	}
	s.closeOnce.Do(func() {
		close(s.done)
	})
}

type Hub struct {
	mu           sync.RWMutex
	subscribers  map[string]map[uint64]*subscriber
	recentByUser map[string][]Event
	nextID       uint64
	buffer       int
	replayLimit  int
	logger       *slog.Logger
}

func NewHub(buffer int, logger *slog.Logger) *Hub {
	if buffer <= 0 {
		buffer = DefaultSubscriberSize
	}
	if logger == nil {
		logger = slog.Default()
	}
	return &Hub{
		subscribers:  make(map[string]map[uint64]*subscriber),
		recentByUser: make(map[string][]Event),
		buffer:       buffer,
		replayLimit:  maxInt(buffer*4, DefaultSubscriberSize),
		logger:       logger.With("component", "helicopter-file-hub"),
	}
}

func (h *Hub) Subscribe(userUUID string) (*Subscription, error) {
	return h.SubscribeWithFilter(userUUID, nil)
}

func (h *Hub) SubscribeWithFilter(userUUID string, match func(Event) bool) (*Subscription, error) {
	if h == nil {
		return nil, errors.New("helicopter file hub is not configured")
	}
	userUUID = canonicalUserID(userUUID)
	if userUUID == "" {
		return nil, errors.New("user uuid is required")
	}

	sub := &subscriber{
		events: make(chan Event, h.buffer),
		done:   make(chan struct{}),
		match:  match,
	}

	h.mu.Lock()
	h.nextID++
	subID := h.nextID
	if h.subscribers[userUUID] == nil {
		h.subscribers[userUUID] = make(map[uint64]*subscriber)
	}
	h.subscribers[userUUID][subID] = sub
	backlog := append([]Event(nil), h.recentByUser[userUUID]...)
	h.mu.Unlock()

	for i := range backlog {
		if sub.match != nil && !sub.match(backlog[i]) {
			continue
		}
		enqueueSubscriberEvent(sub, backlog[i])
	}

	var closeOnce sync.Once
	closeFn := func() {
		closeOnce.Do(func() {
			h.mu.Lock()
			defer h.mu.Unlock()

			if byUser, ok := h.subscribers[userUUID]; ok {
				delete(byUser, subID)
				if len(byUser) == 0 {
					delete(h.subscribers, userUUID)
				}
			}
			sub.close()
		})
	}

	return &Subscription{
		events:  sub.events,
		done:    sub.done,
		closeFn: closeFn,
	}, nil
}

func (h *Hub) PublishToUsers(userUUIDs []string, event Event) int {
	if h == nil {
		return 0
	}
	if strings.TrimSpace(event.EventType) == "" {
		event.EventType = DefaultEventType
	}
	if strings.TrimSpace(event.HelicopterID) == "" || strings.TrimSpace(event.HelicopterFileID) == "" {
		return 0
	}

	uniqueUsers := make(map[string]struct{}, len(userUUIDs))
	for i := range userUUIDs {
		userID := canonicalUserID(userUUIDs[i])
		if userID == "" {
			continue
		}
		uniqueUsers[userID] = struct{}{}
	}
	if len(uniqueUsers) == 0 {
		return 0
	}

	delivered := 0
	for userID := range uniqueUsers {
		h.mu.Lock()
		h.recentByUser[userID] = appendRecentEvents(h.recentByUser[userID], event, h.replayLimit)
		h.mu.Unlock()

		h.mu.RLock()
		byUser := h.subscribers[userID]
		if len(byUser) == 0 {
			h.mu.RUnlock()
			continue
		}

		for _, sub := range byUser {
			select {
			case <-sub.done:
				continue
			default:
			}
			if sub.match != nil && !sub.match(event) {
				continue
			}

			if enqueueSubscriberEvent(sub, event) {
				delivered++
			}
		}
		h.mu.RUnlock()
	}
	return delivered
}

func canonicalUserID(raw string) string {
	return strings.ToLower(strings.TrimSpace(raw))
}

func enqueueSubscriberEvent(sub *subscriber, event Event) bool {
	if sub == nil {
		return false
	}
	select {
	case <-sub.done:
		return false
	default:
	}

	select {
	case sub.events <- event:
		return true
	default:
		select {
		case <-sub.events:
		default:
		}
		select {
		case sub.events <- event:
			return true
		default:
			return false
		}
	}
}

func appendRecentEvents(current []Event, event Event, limit int) []Event {
	if limit <= 0 {
		return current[:0]
	}
	current = append(current, event)
	if len(current) <= limit {
		return current
	}
	start := len(current) - limit
	copy(current, current[start:])
	return current[:limit]
}

func maxInt(a, b int) int {
	if a > b {
		return a
	}
	return b
}
