package multi

import (
	"context"
	"sync"
	"testing"

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

type mockSink struct {
	name      string
	mu        sync.Mutex
	writeFunc func(domain.CorrelatedLog) error
	flushFunc func() error
	closeFunc func() error
	reopenFunc func() error
}

func (m *mockSink) Name() string                              { return m.name }
func (m *mockSink) Write(ctx context.Context, log domain.CorrelatedLog) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.writeFunc(log)
}
func (m *mockSink) Flush(ctx context.Context) error { return m.flushFunc() }
func (m *mockSink) Close() error                    { return m.closeFunc() }
func (m *mockSink) Reopen() error                   { 
	if m.reopenFunc != nil {
		return m.reopenFunc()
	}
	return nil
}

func TestMultiSink_Write(t *testing.T) {
	var mu sync.Mutex
	writeCount := 0

	sink1 := &mockSink{
		name: "sink1",
		writeFunc: func(log domain.CorrelatedLog) error {
			mu.Lock()
			writeCount++
			mu.Unlock()
			return nil
		},
		flushFunc: func() error { return nil },
		closeFunc: func() error { return nil },
	}

	sink2 := &mockSink{
		name: "sink2",
		writeFunc: func(log domain.CorrelatedLog) error {
			mu.Lock()
			writeCount++
			mu.Unlock()
			return nil
		},
		flushFunc: func() error { return nil },
		closeFunc: func() error { return nil },
	}

	ms := NewMultiSink(sink1, sink2)

	log := domain.CorrelatedLog{SrcIP: "192.168.1.1"}
	err := ms.Write(context.Background(), log)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if writeCount != 2 {
		t.Errorf("expected 2 writes, got %d", writeCount)
	}
}

func TestMultiSink_Write_OneFails(t *testing.T) {
	sink1 := &mockSink{
		name: "sink1",
		writeFunc: func(log domain.CorrelatedLog) error {
			return nil
		},
		flushFunc: func() error { return nil },
		closeFunc: func() error { return nil },
	}

	sink2 := &mockSink{
		name: "sink2",
		writeFunc: func(log domain.CorrelatedLog) error {
			return context.Canceled
		},
		flushFunc: func() error { return nil },
		closeFunc: func() error { return nil },
	}

	ms := NewMultiSink(sink1, sink2)

	log := domain.CorrelatedLog{SrcIP: "192.168.1.1"}
	err := ms.Write(context.Background(), log)
	if err == nil {
		t.Error("expected error when one sink fails")
	}
}

func TestMultiSink_AddSink(t *testing.T) {
	ms := NewMultiSink()

	sink := &mockSink{
		name: "dynamic",
		writeFunc: func(log domain.CorrelatedLog) error { return nil },
		flushFunc: func() error { return nil },
		closeFunc: func() error { return nil },
	}

	ms.AddSink(sink)

	log := domain.CorrelatedLog{SrcIP: "192.168.1.1"}
	err := ms.Write(context.Background(), log)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestMultiSink_Name(t *testing.T) {
	ms := NewMultiSink()
	if ms.Name() != "multi" {
		t.Errorf("expected name 'multi', got %s", ms.Name())
	}
}

func TestMultiSink_Flush(t *testing.T) {
	flushed := false
	sink := &mockSink{
		name: "test",
		writeFunc: func(log domain.CorrelatedLog) error { return nil },
		flushFunc: func() error {
			flushed = true
			return nil
		},
		closeFunc: func() error { return nil },
	}

	ms := NewMultiSink(sink)
	err := ms.Flush(context.Background())
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !flushed {
		t.Error("expected sink to be flushed")
	}
}

func TestMultiSink_Flush_Error(t *testing.T) {
	sink := &mockSink{
		name: "test",
		writeFunc: func(log domain.CorrelatedLog) error { return nil },
		flushFunc: func() error { return context.Canceled },
		closeFunc: func() error { return nil },
	}

	ms := NewMultiSink(sink)
	err := ms.Flush(context.Background())
	if err != context.Canceled {
		t.Errorf("expected context.Canceled error, got %v", err)
	}
}

func TestMultiSink_Close(t *testing.T) {
	closed := false
	sink := &mockSink{
		name: "test",
		writeFunc: func(log domain.CorrelatedLog) error { return nil },
		flushFunc: func() error { return nil },
		closeFunc: func() error {
			closed = true
			return nil
		},
	}

	ms := NewMultiSink(sink)
	err := ms.Close()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !closed {
		t.Error("expected sink to be closed")
	}
}

func TestMultiSink_Close_Error(t *testing.T) {
	sink := &mockSink{
		name: "test",
		writeFunc: func(log domain.CorrelatedLog) error { return nil },
		flushFunc: func() error { return nil },
		closeFunc: func() error { return context.Canceled },
	}

	ms := NewMultiSink(sink)
	err := ms.Close()
	if err != context.Canceled {
		t.Errorf("expected context.Canceled error, got %v", err)
	}
}

func TestMultiSink_Write_EmptySinks(t *testing.T) {
	ms := NewMultiSink()
	log := domain.CorrelatedLog{SrcIP: "192.168.1.1"}
	err := ms.Write(context.Background(), log)
	if err != nil {
		t.Fatalf("unexpected error with empty sinks: %v", err)
	}
}

func TestMultiSink_Write_ContextCancelled(t *testing.T) {
	sink := &mockSink{
		name: "test",
		writeFunc: func(log domain.CorrelatedLog) error {
			<-context.Background().Done()
			return nil
		},
		flushFunc: func() error { return nil },
		closeFunc: func() error { return nil },
	}

	ms := NewMultiSink(sink)
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	
	log := domain.CorrelatedLog{SrcIP: "192.168.1.1"}
	err := ms.Write(ctx, log)
	if err != context.Canceled {
		t.Errorf("expected context.Canceled error, got %v", err)
	}
}
