package filemanagerrealtime

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

const (
	FileStatusChangedEventType = "file_status_changed"
	defaultSubscriberBuffer    = 32
)

type FileStatusEvent struct {
	EventType     string  `json:"event_type"`
	FileUUID      string  `json:"file_uuid"`
	FolderUUID    *string `json:"folder_uuid,omitempty"`
	Status        string  `json:"status"`
	Name          string  `json:"name"`
	MimeType      string  `json:"mime_type,omitempty"`
	FailureReason *string `json:"failure_reason"`
	UpdatedAt     string  `json:"updated_at"`
}

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

func (s *Subscription) Events() <-chan FileStatusEvent {
	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 statusSubscriber struct {
	events    chan FileStatusEvent
	done      chan struct{}
	match     func(FileStatusEvent) bool
	closeOnce sync.Once
}

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

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

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

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

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

	sub := &statusSubscriber{
		events: make(chan FileStatusEvent, 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]*statusSubscriber)
	}
	h.subscribers[userUUID][subID] = sub
	backlog := append([]FileStatusEvent(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 FileStatusEvent) int {
	if h == nil {
		return 0
	}
	if strings.TrimSpace(event.EventType) == "" {
		event.EventType = FileStatusChangedEventType
	}
	if strings.TrimSpace(event.FileUUID) == "" || strings.TrimSpace(event.Status) == "" {
		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 (h *Hub) SubscriberCount(userUUID string) int {
	if h == nil {
		return 0
	}
	userUUID = canonicalUserID(userUUID)
	if userUUID == "" {
		return 0
	}
	h.mu.RLock()
	defer h.mu.RUnlock()
	return len(h.subscribers[userUUID])
}

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

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

	select {
	case sub.events <- event:
		return true
	default:
		// Drop the oldest buffered event to prioritize fresh status changes.
		select {
		case <-sub.events:
		default:
		}
		select {
		case sub.events <- event:
			return true
		default:
			return false
		}
	}
}

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

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