package aoiprxkit

import (
	"context"
	"fmt"
	"sync"
	"time"
)

// StreamFinder keeps a live in-memory view of SAP/SDP announcements
// and can wait for sessions by their SDP "s=" session name.
type StreamFinder struct {
	listener *SAPListener

	mu       sync.Mutex
	sessions map[string]SAPAnnouncement
	waiters  map[string][]chan SAPAnnouncement
}

func NewStreamFinder(cfg SAPListenerConfig) (*StreamFinder, error) {
	sf := &StreamFinder{
		sessions: make(map[string]SAPAnnouncement),
		waiters:  make(map[string][]chan SAPAnnouncement),
	}
	listener, err := NewSAPListener(cfg, sf.handleAnnouncement)
	if err != nil {
		return nil, err
	}
	sf.listener = listener
	return sf, nil
}

func (s *StreamFinder) Start(ctx context.Context) error {
	return s.listener.Start(ctx)
}

func (s *StreamFinder) Stop() error {
	return s.listener.Stop()
}

func (s *StreamFinder) handleAnnouncement(a SAPAnnouncement) {
	name := a.ParsedSDP.SessionName
	if name == "" {
		return
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	if a.Delete {
		delete(s.sessions, name)
		return
	}

	s.sessions[name] = a
	if waiters := s.waiters[name]; len(waiters) > 0 {
		delete(s.waiters, name)
		for _, ch := range waiters {
			select {
			case ch <- a:
			default:
			}
			close(ch)
		}
	}
}

func (s *StreamFinder) FindByStreamName(name string) (SAPAnnouncement, bool) {
	s.mu.Lock()
	defer s.mu.Unlock()
	a, ok := s.sessions[name]
	return a, ok
}

func (s *StreamFinder) WaitForStreamName(ctx context.Context, name string) (SAPAnnouncement, error) {
	if name == "" {
		return SAPAnnouncement{}, fmt.Errorf("stream name must not be empty")
	}

	s.mu.Lock()
	if a, ok := s.sessions[name]; ok {
		s.mu.Unlock()
		return a, nil
	}
	ch := make(chan SAPAnnouncement, 1)
	s.waiters[name] = append(s.waiters[name], ch)
	s.mu.Unlock()

	select {
	case <-ctx.Done():
		s.mu.Lock()
		waiters := s.waiters[name]
		kept := waiters[:0]
		for _, w := range waiters {
			if w != ch {
				kept = append(kept, w)
			}
		}
		if len(kept) == 0 {
			delete(s.waiters, name)
		} else {
			s.waiters[name] = kept
		}
		s.mu.Unlock()
		return SAPAnnouncement{}, ctx.Err()
	case a := <-ch:
		return a, nil
	}
}

func (s *StreamFinder) WaitConfigByStreamName(ctx context.Context, base Config, name string) (Config, SAPAnnouncement, error) {
	a, err := s.WaitForStreamName(ctx, name)
	if err != nil {
		return Config{}, SAPAnnouncement{}, err
	}
	cfg, err := ConfigFromSDP(base, a.ParsedSDP)
	if err != nil {
		return Config{}, SAPAnnouncement{}, err
	}
	return cfg, a, nil
}

func (s *StreamFinder) Snapshot() []SAPAnnouncement {
	s.mu.Lock()
	defer s.mu.Unlock()
	out := make([]SAPAnnouncement, 0, len(s.sessions))
	for _, v := range s.sessions {
		out = append(out, v)
	}
	return out
}

func (s *StreamFinder) WaitForStreamNameTimeout(name string, timeout time.Duration) (SAPAnnouncement, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	return s.WaitForStreamName(ctx, name)
}
