package ws

import (
	"fmt"
	"reflect"
	"time"

	"github.com/gorilla/websocket"
)

const (
	defaultSendTimeout                 = 10 * time.Second
	defaultMaxInputMessageSize   int64 = 10 * 1024 * 1024 // 4 MB
	defaultDisconnectWaitTimeout       = 4 * time.Second
	defaultLostConnectionTimeout       = 1 * time.Minute

	emptyIn    = 0
	userInOnly = 1
	connInOnly = 2
	bothIn     = 3

	privateUserOnly        = 10
	privateUserAndT        = 11
	connAndPrivateUser     = 12
	connAndPrivateUserAndT = 13

	BadMessageCode         = 4000
	NoSessionCode          = 4001
	UnknownMessageTypeCode = 4002
)

var (
	connType = reflect.TypeFor[*Conn]()

	closeCode2Text = map[int]string{
		BadMessageCode:                   "bad message",
		NoSessionCode:                    "no session",
		UnknownMessageTypeCode:           "unknown message type",
		websocket.CloseInternalServerErr: "internal server error",
		websocket.CloseNormalClosure:     "normal closure",
	}
)

type BadMessage struct {
	Error   string `json:"error"`
	Message []byte `json:"message"`
}

type MessageCodec interface {
	EncodeMessage(string, any) ([]byte, error)
	DecodeMessageHeader([]byte) (string, []byte, error)
	DecodeMessageBody([]byte, reflect.Type) (reflect.Value, error)
}

type funcData struct {
	Func        reflect.Value
	ParamType   reflect.Type
	InSignature int
}

func getPublicFuncData(f any) (_ funcData) {
	procT := reflect.TypeOf(f)
	if procT.Kind() != reflect.Func {
		panic(fmt.Sprintf("incorrect 2nd param type '%T'. Must be func\n", f))
	}
	wrongSignature := fmt.Sprintf("handle func '%T' must have signature func(*ws.Conn, T) or func(*ws.Conn) or func() or func(T)\n", f)
	fd := funcData{
		Func: reflect.ValueOf(f),
	}
	switch procT.NumIn() {
	case 0:
		fd.InSignature = emptyIn
	case 1:
		paramType := procT.In(0)
		if paramType == connType {
			fd.InSignature = connInOnly
		} else {
			fd.ParamType = paramType
			fd.InSignature = userInOnly
		}
	case 2:
		if procT.In(0) != connType {
			panic(wrongSignature)
		}
		fd.ParamType = procT.In(1)
		fd.InSignature = bothIn
	default:
		panic(wrongSignature)
	}
	return fd
}

func getPrivateFuncData(f any) (_ funcData) {
	procT := reflect.TypeOf(f)
	if procT.Kind() != reflect.Func {
		panic(fmt.Sprintf("incorrect 2nd param type '%T'. Must be func\n", f))
	}
	wrongSignature := fmt.Sprintf("handle func '%T' must have signature func(*ws.Conn, int64, T) or func(*ws.Conn, int64) or func(int64) or func(int64, T)\n", f)
	fd := funcData{
		Func: reflect.ValueOf(f),
	}
	switch procT.NumIn() {
	case 1:
		if procT.In(0).Kind() != reflect.Int64 {
			panic(wrongSignature)
		}
		fd.InSignature = privateUserOnly
	case 2:
		firstType := procT.In(0)
		if firstType == connType {
			if procT.In(1).Kind() != reflect.Int64 {
				panic(wrongSignature)
			}
			fd.InSignature = connAndPrivateUser
		} else {
			if firstType.Kind() != reflect.Int64 {
				panic(wrongSignature)
			}
			fd.ParamType = procT.In(1)
			fd.InSignature = privateUserAndT
		}
	case 3:
		if procT.In(0) != connType {
			panic(wrongSignature)
		}
		if procT.In(1).Kind() != reflect.Int64 {
			panic(wrongSignature)
		}
		fd.ParamType = procT.In(2)
		fd.InSignature = connAndPrivateUserAndT
	default:
		panic(wrongSignature)
	}
	return fd
}
