package wrapper

import (
	"context"
	"io"
	"net"
	"syscall"
	"testing"
	"time"

	xio "github.com/go-gost/x/internal/io"
)

type connWithCloseRead struct {
	net.Conn
	closeReadCalled bool
}

func (c *connWithCloseRead) CloseRead() error {
	c.closeReadCalled = true
	return nil
}

type connWithCloseWrite struct {
	net.Conn
	closeWriteCalled bool
}

func (c *connWithCloseWrite) CloseWrite() error {
	c.closeWriteCalled = true
	return nil
}

type connWithSyscall struct {
	net.Conn
}

func (c *connWithSyscall) SyscallConn() (syscall.RawConn, error) {
	return nil, nil
}

type connWithContext struct {
	net.Conn
}

func (c *connWithContext) Context() context.Context {
	return context.Background()
}

func TestWrapConn_NilLimiter(t *testing.T) {
	c := &mockConn{}
	wc := WrapConn(nil, c)
	if wc != c {
		t.Fatal("WrapConn should return original conn when limiter is nil")
	}
}

func TestWrapConn_WithLimiter(t *testing.T) {
	c := &mockConn{}
	lim := &allowLimiter{limit: 5}
	wc := WrapConn(lim, c)

	sc, ok := wc.(*serverConn)
	if !ok {
		t.Fatal("WrapConn should return *serverConn when limiter is not nil")
	}
	if sc.Conn != c {
		t.Fatal("inner conn should be the original")
	}
	if sc.limiter != lim {
		t.Fatal("limiter should be set")
	}
}

func TestServerConn_Close_ReleasesLimiter(t *testing.T) {
	c := &mockConn{}
	lim := &allowLimiter{limit: 1}
	lim.Allow(1) // acquire one

	sc := &serverConn{Conn: c, limiter: lim}
	if err := sc.Close(); err != nil {
		t.Fatal(err)
	}
	if !c.closed {
		t.Fatal("inner conn should be closed")
	}
	// After Close, the limiter should have one slot released.
	if !lim.Allow(1) {
		t.Fatal("limiter should allow after close released a slot")
	}
}

func TestServerConn_CloseRead_Supported(t *testing.T) {
	inner := &connWithCloseRead{Conn: &mockConn{}}
	sc := &serverConn{Conn: inner}
	if err := sc.CloseRead(); err != nil {
		t.Fatal(err)
	}
	if !inner.closeReadCalled {
		t.Fatal("CloseRead should delegate to inner conn")
	}
}

func TestServerConn_CloseRead_Unsupported(t *testing.T) {
	inner := &mockConn{}
	sc := &serverConn{Conn: inner}
	err := sc.CloseRead()
	if err != xio.ErrUnsupported {
		t.Fatalf("expected ErrUnsupported, got %v", err)
	}
}

func TestServerConn_CloseWrite_Supported(t *testing.T) {
	inner := &connWithCloseWrite{Conn: &mockConn{}}
	sc := &serverConn{Conn: inner}
	if err := sc.CloseWrite(); err != nil {
		t.Fatal(err)
	}
	if !inner.closeWriteCalled {
		t.Fatal("CloseWrite should delegate to inner conn")
	}
}

func TestServerConn_CloseWrite_Unsupported(t *testing.T) {
	inner := &mockConn{}
	sc := &serverConn{Conn: inner}
	err := sc.CloseWrite()
	if err != xio.ErrUnsupported {
		t.Fatalf("expected ErrUnsupported, got %v", err)
	}
}

func TestServerConn_SyscallConn_Supported(t *testing.T) {
	inner := &connWithSyscall{Conn: &mockConn{}}
	sc := &serverConn{Conn: inner}
	_, err := sc.SyscallConn()
	if err != nil {
		t.Fatalf("expected success, got %v", err)
	}
}

func TestServerConn_SyscallConn_Unsupported(t *testing.T) {
	inner := &mockConn{}
	sc := &serverConn{Conn: inner}
	_, err := sc.SyscallConn()
	if err != errUnsupport {
		t.Fatalf("expected errUnsupport, got %v", err)
	}
}

func TestServerConn_Context_Supported(t *testing.T) {
	inner := &connWithContext{Conn: &mockConn{}}
	sc := &serverConn{Conn: inner}
	ctx := sc.Context()
	if ctx == nil {
		t.Fatal("Context should not be nil when inner conn supports it")
	}
}

func TestServerConn_Context_Unsupported(t *testing.T) {
	inner := &mockConn{}
	sc := &serverConn{Conn: inner}
	ctx := sc.Context()
	if ctx == nil {
		t.Fatal("Context should return context.Background(), not nil")
	}
}

func TestServerConn_ReadWrite(t *testing.T) {
	inner := &mockConn{}
	sc := &serverConn{Conn: inner}

	n, err := sc.Read(nil)
	if err != nil {
		t.Fatal(err)
	}
	if n != 0 {
		t.Fatalf("expected 0, got %d", n)
	}

	n, err = sc.Write([]byte("hello"))
	if err != nil {
		t.Fatal(err)
	}
	if n != 5 {
		t.Fatalf("expected 5, got %d", n)
	}
}

// Ensure unused imports are fine:
var _ io.ReadWriter = (*serverConn)(nil)
var _ net.Conn = (*serverConn)(nil)
var _ = time.Now
