package multi

import (
	"context"
	"sync"

	"github.com/antitbone/ja4/correlator/internal/domain"
	"github.com/antitbone/ja4/correlator/internal/ports"
)

// MultiSink fans out correlated logs to multiple sinks.
type MultiSink struct {
	mu    sync.RWMutex
	sinks []ports.CorrelatedLogSink
}

// NewMultiSink creates a new multi-sink.
func NewMultiSink(sinks ...ports.CorrelatedLogSink) *MultiSink {
	return &MultiSink{
		sinks: sinks,
	}
}

// Name returns the sink name.
func (s *MultiSink) Name() string {
	return "multi"
}

// AddSink adds a sink to the fan-out.
func (s *MultiSink) AddSink(sink ports.CorrelatedLogSink) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.sinks = append(s.sinks, sink)
}

// Write writes a correlated log to all sinks concurrently.
// Returns the first error encountered (but all sinks are attempted).
func (s *MultiSink) Write(ctx context.Context, log domain.CorrelatedLog) error {
	s.mu.RLock()
	sinks := make([]ports.CorrelatedLogSink, len(s.sinks))
	copy(sinks, s.sinks)
	s.mu.RUnlock()

	if len(sinks) == 0 {
		return nil
	}

	var wg sync.WaitGroup
	var firstErr error
	var firstErrMu sync.Mutex
	errChan := make(chan error, len(sinks))

	for _, sink := range sinks {
		wg.Add(1)
		go func(sk ports.CorrelatedLogSink) {
			defer wg.Done()
			if err := sk.Write(ctx, log); err != nil {
				// Non-blocking send to errChan
				select {
				case errChan <- err:
				default:
					// Channel full, error will be handled via firstErr
				}
			}
		}(sink)
	}

	// Wait for all writes to complete in a separate goroutine
	done := make(chan struct{})
	go func() {
		wg.Wait()
		close(done)
	}()

	// Collect errors with timeout
	select {
	case <-done:
		close(errChan)
		// Collect first error
		for err := range errChan {
			if err != nil {
				firstErrMu.Lock()
				if firstErr == nil {
					firstErr = err
				}
				firstErrMu.Unlock()
			}
		}
	case <-ctx.Done():
		return ctx.Err()
	}

	firstErrMu.Lock()
	defer firstErrMu.Unlock()
	return firstErr
}

// Flush flushes all sinks.
func (s *MultiSink) Flush(ctx context.Context) error {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, sink := range s.sinks {
		if err := sink.Flush(ctx); err != nil {
			return err
		}
	}
	return nil
}

// Close closes all sinks.
func (s *MultiSink) Close() error {
	s.mu.RLock()
	defer s.mu.RUnlock()

	var firstErr error
	for _, sink := range s.sinks {
		if err := sink.Close(); err != nil && firstErr == nil {
			firstErr = err
		}
	}
	return firstErr
}

// Reopen reopens all sinks (for log rotation on SIGHUP).
func (s *MultiSink) Reopen() error {
	s.mu.RLock()
	defer s.mu.RUnlock()

	var firstErr error
	for _, sink := range s.sinks {
		if err := sink.Reopen(); err != nil && firstErr == nil {
			firstErr = err
		}
	}
	return firstErr
}
