package ldap

import (
	"errors"
	"net"
	"strings"
	"testing"
	"time"

	"gopkg.in/asn1-ber.v1"
)

// TestNilPacket tests that nil packets don't cause a panic.
func TestNilPacket(t *testing.T) {
	// Test for nil packet
	code, _ := getLDAPResultCode(nil)
	if code != ErrorUnexpectedResponse {
		t.Errorf("Should have an 'ErrorUnexpectedResponse' error in nil packets, got: %v", code)
	}

	// Test for nil result
	kids := []*ber.Packet{
		{},  // Unused
		nil, // Can't be nil
	}
	pack := &ber.Packet{Children: kids}
	code, _ = getLDAPResultCode(pack)

	if code != ErrorUnexpectedResponse {
		t.Errorf("Should have an 'ErrorUnexpectedResponse' error in nil packets, got: %v", code)
	}
}

// TestConnReadErr tests that an unexpected error reading from underlying
// connection bubbles up to the goroutine which makes a request.
func TestConnReadErr(t *testing.T) {
	conn := &signalErrConn{
		signals: make(chan error),
	}

	ldapConn := NewConn(conn, false)
	ldapConn.Start()

	// Make a dummy search request.
	searchReq := NewSearchRequest("dc=example,dc=com", ScopeWholeSubtree, DerefAlways, 0, 0, false, "(objectClass=*)", nil, nil)

	expectedError := errors.New("this is the error you are looking for")

	// Send the signal after a short amount of time.
	time.AfterFunc(10*time.Millisecond, func() { conn.signals <- expectedError })

	// This should block until the underlying conn gets the error signal
	// which should bubble up through the reader() goroutine, close the
	// connection, and
	_, err := ldapConn.Search(searchReq)
	if err == nil || !strings.Contains(err.Error(), expectedError.Error()) {
		t.Errorf("not the expected error: %s", err)
	}
}

// signalErrConn is a helpful type used with TestConnReadErr. It implements the
// net.Conn interface to be used as a connection for the test. Most methods are
// no-ops but the Read() method blocks until it receives a signal which it
// returns as an error.
type signalErrConn struct {
	signals chan error
}

// Read blocks until an error is sent on the internal signals channel. That
// error is returned.
func (c *signalErrConn) Read(b []byte) (n int, err error) {
	return 0, <-c.signals
}

func (c *signalErrConn) Write(b []byte) (n int, err error) {
	return len(b), nil
}

func (c *signalErrConn) Close() error {
	close(c.signals)
	return nil
}

func (c *signalErrConn) LocalAddr() net.Addr {
	return (*net.TCPAddr)(nil)
}

func (c *signalErrConn) RemoteAddr() net.Addr {
	return (*net.TCPAddr)(nil)
}

func (c *signalErrConn) SetDeadline(t time.Time) error {
	return nil
}

func (c *signalErrConn) SetReadDeadline(t time.Time) error {
	return nil
}

func (c *signalErrConn) SetWriteDeadline(t time.Time) error {
	return nil
}
