package shadowsocks

import (
	"crypto/md5"
	"net"

	"github.com/metacubex/sing/common"
	E "github.com/metacubex/sing/common/exceptions"
	F "github.com/metacubex/sing/common/format"
	M "github.com/metacubex/sing/common/metadata"
	N "github.com/metacubex/sing/common/network"
)

var (
	ErrBadKey          = E.New("bad key")
	ErrMissingPassword = E.New("missing password")
	ErrNoUsers         = E.New("no users")
)

type Method interface {
	Name() string
	DialConn(conn net.Conn, destination M.Socksaddr) (net.Conn, error)
	DialEarlyConn(conn net.Conn, destination M.Socksaddr) net.Conn
	DialPacketConn(conn net.Conn) N.NetPacketConn
}

type Service interface {
	Name() string
	Password() string
	N.TCPConnectionHandler
	N.UDPHandler
	E.Handler
}

type MultiService[U comparable] interface {
	Name() string
	UpdateUsers(userList []U, keyList [][]byte) error
	UpdateUsersWithPasswords(userList []U, passwordList []string) error
	N.TCPConnectionHandler
	N.UDPHandler
	E.Handler
}

type Handler interface {
	N.TCPConnectionHandler
	N.UDPConnectionHandler
	E.Handler
}

type ServerConnError struct {
	net.Conn
	Source M.Socksaddr
	Cause  error
}

func (e *ServerConnError) Close() error {
	if conn, ok := common.Cast[*net.TCPConn](e.Conn); ok {
		conn.SetLinger(0)
	}
	return e.Conn.Close()
}

func (e *ServerConnError) Unwrap() error {
	return e.Cause
}

func (e *ServerConnError) Error() string {
	return F.ToString("shadowsocks: serve TCP from ", e.Source, ": ", e.Cause)
}

type ServerPacketError struct {
	Source M.Socksaddr
	Cause  error
}

func (e *ServerPacketError) Unwrap() error {
	return e.Cause
}

func (e *ServerPacketError) Error() string {
	return F.ToString("shadowsocks: serve UDP from ", e.Source, ": ", e.Cause)
}

func Key(password []byte, keySize int) []byte {
	var b, prev []byte
	h := md5.New()
	for len(b) < keySize {
		h.Write(prev)
		h.Write(password)
		b = h.Sum(b)
		prev = b[len(b)-h.Size():]
		h.Reset()
	}
	return b[:keySize]
}
