package neffos

import (
	"fmt"
	"io"
	"net"
	"os"
	"strings"
)

// MessageHandlerFunc is the definition type of the events' callback.
// Its error can be written to the other side on specific events,
// i.e on `OnNamespaceConnect` it will abort a remote namespace connection.
// See examples for more.
type MessageHandlerFunc func(*NSConn, Message) error

var (
	// OnNamespaceConnect is the event name which its callback is fired right before namespace connect,
	// if non-nil error then the remote connection's `Conn.Connect` will fail and send that error text.
	// Connection is not ready to emit data to the namespace.
	OnNamespaceConnect = "_OnNamespaceConnect"
	// OnNamespaceConnected is the event name which its callback is fired after namespace successfully connected.
	// Connection is ready to emit data back to the namespace.
	OnNamespaceConnected = "_OnNamespaceConnected"
	// OnNamespaceDisconnect is the event name which its callback is fired when
	// remote namespace disconnection or local namespace disconnection is happening.
	// For server-side connections the reply matters, so if error returned then the client-side cannot disconnect yet,
	// for client-side the return value does not matter.
	OnNamespaceDisconnect = "_OnNamespaceDisconnect" // if allowed to connect then it's allowed to disconnect as well.
	// OnRoomJoin is the event name which its callback is fired right before room join.
	OnRoomJoin = "_OnRoomJoin" // able to check if allowed to join.
	// OnRoomJoined is the event name which its callback is fired after the connection has successfully joined to a room.
	OnRoomJoined = "_OnRoomJoined" // able to broadcast messages to room.
	// OnRoomLeave is the event name which its callback is fired right before room leave.
	OnRoomLeave = "_OnRoomLeave" // able to broadcast bye-bye messages to room.
	// OnRoomLeft is the event name which its callback is fired after the connection has successfully left from a room.
	OnRoomLeft = "_OnRoomLeft" // if allowed to join to a room, then its allowed to leave from it.
	// OnAnyEvent is the event name which its callback is fired when incoming message's event is not declared to the ConnHandler(`Events` or `Namespaces`).
	OnAnyEvent = "_OnAnyEvent" // when event no match.
	// OnNativeMessage is fired on incoming native/raw websocket messages.
	// If this event defined then an incoming message can pass the check (it's an invalid message format)
	// with just the Message's Body filled, the Event is "OnNativeMessage" and IsNative always true.
	// This event should be defined under an empty namespace in order this to work.
	OnNativeMessage = "_OnNativeMessage"
)

// IsSystemEvent reports whether the "event" is a system event,
// OnNamespaceConnect, OnNamespaceConnected, OnNamespaceDisconnect,
// OnRoomJoin, OnRoomJoined, OnRoomLeave and OnRoomLeft.
func IsSystemEvent(event string) bool {
	switch event {
	case OnNamespaceConnect, OnNamespaceConnected, OnNamespaceDisconnect,
		OnRoomJoin, OnRoomJoined, OnRoomLeave, OnRoomLeft:
		return true
	default:
		return false
	}
}

// CloseError can be used to send and close a remote connection in the event callback's return statement.
type CloseError struct {
	error
	Code int
}

func (err CloseError) Error() string {
	return fmt.Sprintf("[%d] %s", err.Code, err.error.Error())
}

// IsDisconnectError reports whether the "err" is a timeout or a closed connection error.
func IsDisconnectError(err error) bool {
	if err == nil {
		return false
	}

	return IsCloseError(err) || IsTimeoutError(err)
}

func isManualCloseError(err error) bool {
	if _, ok := err.(CloseError); ok {
		return true
	}

	return false
}

// IsCloseError reports whether the "err" is a "closed by the remote host" network connection error.
func IsCloseError(err error) bool {
	if err == nil {
		return false
	}

	if isManualCloseError(err) {
		return true
	}

	if err == io.ErrUnexpectedEOF || err == io.EOF {
		return true
	}

	if netErr, ok := err.(*net.OpError); ok {
		if netErr.Err == nil {
			return false
		}

		if sysErr, ok := netErr.Err.(*os.SyscallError); ok {
			return sysErr != nil
			// return strings.HasSuffix(sysErr.Err.Error(), "closed by the remote host.")
		}

		return strings.HasSuffix(err.Error(), "use of closed network connection")
	}

	return false
}

// IsTimeoutError reports whether the "err" is caused by a defined timeout.
func IsTimeoutError(err error) bool {
	if err == nil {
		return false
	}

	if netErr, ok := err.(*net.OpError); ok {
		// poll.TimeoutError is the /internal/poll of the go language itself, we can't use it directly.
		return netErr.Timeout()
	}

	return false
}

type reply struct {
	Body []byte
}

func (r reply) Error() string {
	return ""
}

func isReply(err error) ([]byte, bool) {
	if err != nil {
		if r, ok := err.(reply); ok {
			return r.Body, true
		}
	}
	return nil, false
}

// Reply is a special type of custom error which sends a message back to the other side
// with the exact same incoming Message's Namespace (and Room if specified)
// except its body which would be the given "body".
func Reply(body []byte) error {
	return reply{body}
}
