package interfaces

import (
	"bytes"
	"net"
	"sync"
	"testing"
	"time"

	"git.quad4.io/Networks/Reticulum-Go/pkg/common"
)

func TestBaseInterfaceStateChanges(t *testing.T) {
	bi := NewBaseInterface("test", common.IF_TYPE_TCP, false) // Start disabled

	if bi.IsEnabled() {
		t.Error("Newly created disabled interface reports IsEnabled() == true")
	}
	if bi.IsOnline() {
		t.Error("Newly created disabled interface reports IsOnline() == true")
	}
	if bi.IsDetached() {
		t.Error("Newly created interface reports IsDetached() == true")
	}

	bi.Enable()
	if !bi.IsEnabled() {
		t.Error("After Enable(), IsEnabled() == false")
	}
	if !bi.IsOnline() {
		t.Error("After Enable(), IsOnline() == false")
	}
	if bi.IsDetached() {
		t.Error("After Enable(), IsDetached() == true")
	}

	bi.Detach()
	if bi.IsEnabled() {
		t.Error("After Detach(), IsEnabled() == true")
	}
	if bi.IsOnline() {
		t.Error("After Detach(), IsOnline() == true")
	}
	if !bi.IsDetached() {
		t.Error("After Detach(), IsDetached() == false")
	}

	// Reset for Disable test
	bi = NewBaseInterface("test2", common.IF_TYPE_UDP, true) // Start enabled
	if !bi.Enabled {                                         // Check the Enabled field directly first
		t.Error("Newly created enabled interface reports Enabled == false")
	}
	if bi.IsEnabled() { // IsEnabled should still be false because Online is false
		t.Error("Newly created enabled interface reports IsEnabled() == true before Enable() is called")
	}

	bi.Enable()          // Explicitly enable to set Online = true
	if !bi.IsEnabled() { // Now IsEnabled should be true
		t.Error("After Enable() on initially enabled interface, IsEnabled() == false")
	}

	bi.Disable()
	if bi.Enabled { // Check Enabled field after Disable()
		t.Error("After Disable(), Enabled == true")
	}
	if bi.IsOnline() {
		t.Error("After Disable(), IsOnline() == true")
	}
	if bi.IsDetached() { // Disable doesn't detach
		t.Error("After Disable(), IsDetached() == true")
	}
}

func TestBaseInterfaceGetters(t *testing.T) {
	bi := NewBaseInterface("getterTest", common.IF_TYPE_AUTO, true)

	if bi.GetName() != "getterTest" {
		t.Errorf("GetName() = %s; want getterTest", bi.GetName())
	}
	if bi.GetType() != common.IF_TYPE_AUTO {
		t.Errorf("GetType() = %v; want %v", bi.GetType(), common.IF_TYPE_AUTO)
	}
	if bi.GetMode() != common.IF_MODE_FULL {
		t.Errorf("GetMode() = %v; want %v", bi.GetMode(), common.IF_MODE_FULL)
	}
	if bi.GetMTU() != common.DEFAULT_MTU { // Assuming default MTU
		t.Errorf("GetMTU() = %d; want %d", bi.GetMTU(), common.DEFAULT_MTU)
	}
}

func TestBaseInterfaceCallbacks(t *testing.T) {
	bi := NewBaseInterface("callbackTest", common.IF_TYPE_TCP, true)
	var wg sync.WaitGroup
	var callbackCalled bool

	callback := func(data []byte, iface common.NetworkInterface) {
		if len(data) != 5 {
			t.Errorf("Callback received data length %d; want 5", len(data))
		}
		if iface.GetName() != "callbackTest" {
			t.Errorf("Callback received interface name %s; want callbackTest", iface.GetName())
		}
		callbackCalled = true
		wg.Done()
	}

	bi.SetPacketCallback(callback)
	if bi.GetPacketCallback() == nil { // Cannot directly compare functions
		t.Error("GetPacketCallback() returned nil after SetPacketCallback()")
	}

	wg.Add(1)
	go bi.ProcessIncoming([]byte{1, 2, 3, 4, 5}) // Run in goroutine as callback might block

	// Wait for callback or timeout
	waitTimeout(&wg, 1*time.Second, t)

	if !callbackCalled {
		t.Error("Packet callback was not called after ProcessIncoming")
	}
}

func TestBaseInterfaceStats(t *testing.T) {
	bi := NewBaseInterface("statsTest", common.IF_TYPE_UDP, true)
	bi.Enable() // Need to be Online for ProcessOutgoing

	data1 := []byte{1, 2, 3}
	data2 := []byte{4, 5, 6, 7, 8}

	bi.ProcessIncoming(data1)
	if bi.RxBytes != uint64(len(data1)) {
		t.Errorf("RxBytes = %d; want %d after first ProcessIncoming", bi.RxBytes, len(data1))
	}

	bi.ProcessIncoming(data2)
	if bi.RxBytes != uint64(len(data1)+len(data2)) {
		t.Errorf("RxBytes = %d; want %d after second ProcessIncoming", bi.RxBytes, len(data1)+len(data2))
	}

	// ProcessOutgoing only updates TxBytes in BaseInterface
	err := bi.ProcessOutgoing(data1)
	if err != nil {
		t.Fatalf("ProcessOutgoing failed: %v", err)
	}
	if bi.TxBytes != uint64(len(data1)) {
		t.Errorf("TxBytes = %d; want %d after first ProcessOutgoing", bi.TxBytes, len(data1))
	}

	err = bi.ProcessOutgoing(data2)
	if err != nil {
		t.Fatalf("ProcessOutgoing failed: %v", err)
	}
	if bi.TxBytes != uint64(len(data1)+len(data2)) {
		t.Errorf("TxBytes = %d; want %d after second ProcessOutgoing", bi.TxBytes, len(data1)+len(data2))
	}
}

// Helper function to wait for a WaitGroup with a timeout
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration, t *testing.T) {
	c := make(chan struct{})
	go func() {
		defer close(c)
		wg.Wait()
	}()
	select {
	case <-c:
		// Completed normally
	case <-time.After(timeout):
		t.Fatal("Timed out waiting for WaitGroup")
	}
}

// Minimal mock interface for InterceptedInterface test
type mockInterface struct {
	BaseInterface
	sendCalled bool
	sendData   []byte
}

func (m *mockInterface) Send(data []byte, addr string) error {
	m.sendCalled = true
	m.sendData = data
	return nil
}

func (m *mockInterface) GetType() common.InterfaceType                  { return common.IF_TYPE_NONE }
func (m *mockInterface) GetMode() common.InterfaceMode                  { return common.IF_MODE_FULL }
func (m *mockInterface) ProcessIncoming(data []byte)                    {}
func (m *mockInterface) ProcessOutgoing(data []byte) error              { return nil }
func (m *mockInterface) SendPathRequest([]byte) error                   { return nil }
func (m *mockInterface) SendLinkPacket([]byte, []byte, time.Time) error { return nil }
func (m *mockInterface) Start() error                                   { return nil }
func (m *mockInterface) Stop() error                                    { return nil }
func (m *mockInterface) GetConn() net.Conn                              { return nil }
func (m *mockInterface) GetBandwidthAvailable() bool                    { return true }

func TestInterceptedInterface(t *testing.T) {
	mockBase := &mockInterface{}
	var interceptorCalled bool
	var interceptedData []byte

	interceptor := func(data []byte, iface common.NetworkInterface) error {
		interceptorCalled = true
		interceptedData = data
		return nil
	}

	intercepted := NewInterceptedInterface(mockBase, interceptor)

	testData := []byte("intercept me")
	err := intercepted.Send(testData, "dummy_addr")
	if err != nil {
		t.Fatalf("Intercepted Send failed: %v", err)
	}

	if !interceptorCalled {
		t.Error("Interceptor function was not called")
	}
	if !bytes.Equal(interceptedData, testData) {
		t.Errorf("Interceptor received data %x; want %x", interceptedData, testData)
	}

	if !mockBase.sendCalled {
		t.Error("Original Send function was not called")
	}
	if !bytes.Equal(mockBase.sendData, testData) {
		t.Errorf("Original Send received data %x; want %x", mockBase.sendData, testData)
	}
}
