// Package protocol defines the INP2P wire protocol.
//
// Message format:  [Header 8B] + [JSON payload]
//
//	Header: DataLen(uint32 LE) + MainType(uint16 LE) + SubType(uint16 LE)
//	DataLen = len(header) + len(payload) = 8 + len(json)
package protocol

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"io"
)

// HeaderSize is the fixed 8-byte message header.
const HeaderSize = 8

// ─── Main message types ───

const (
	MsgLogin     uint16 = 1
	MsgHeartbeat uint16 = 2
	MsgNAT       uint16 = 3
	MsgPush      uint16 = 4 // signaling push (punch/relay coordination)
	MsgRelay     uint16 = 5
	MsgReport    uint16 = 6
	MsgTunnel    uint16 = 7 // in-tunnel control messages
)

// ─── Sub types: MsgLogin ───

const (
	SubLoginReq uint16 = iota
	SubLoginRsp
)

// ─── Sub types: MsgHeartbeat ───

const (
	SubHeartbeatPing uint16 = iota
	SubHeartbeatPong
)

// ─── Sub types: MsgNAT ───

const (
	SubNATDetectReq uint16 = iota
	SubNATDetectRsp
)

// ─── Sub types: MsgPush ───

const (
	SubPushConnectReq  uint16 = iota // "please connect to peer X"
	SubPushConnectRsp                // peer's punch parameters
	SubPushPunchStart                // coordinate simultaneous punch
	SubPushPunchResult               // report punch outcome
	SubPushRelayOffer                // relay node offers to relay
	SubPushNodeOnline                // notify: destination came online
	SubPushEditApp                   // add/edit tunnel app
	SubPushDeleteApp                 // delete tunnel app
	SubPushReportApps                // request app list
	SubPushSDWANConfig               // push sdwan config to client
	SubPushSDWANPeer                 // push sdwan peer online/update
	SubPushSDWANDel                  // push sdwan peer offline/delete
	SubPushConfig                    // generic remote config push
)

// Sub types: MsgTunnel
const (
	SubTunnelSDWANData uint16 = iota
	SubTunnelSDWANRaw
)

// ─── Sub types: MsgRelay ───

const (
	SubRelayNodeReq uint16 = iota
	SubRelayNodeRsp
	SubRelayDataReq // establish data channel through relay
	SubRelayDataRsp
)

// ─── Sub types: MsgReport ───

const (
	SubReportBasic   uint16 = iota // OS, version, MAC, etc.
	SubReportApps                  // running tunnels
	SubReportConnect               // connection result
)

// ─── NAT types ───

type NATType int

const (
	NATNone      NATType = 0   // public IP, no NAT
	NATCone      NATType = 1   // full/restricted/port-restricted cone
	NATSymmetric NATType = 2   // symmetric (port changes per dest)
	NATUnknown   NATType = 314 // detection failed / UDP blocked
)

func (n NATType) String() string {
	switch n {
	case NATNone:
		return "None"
	case NATCone:
		return "Cone"
	case NATSymmetric:
		return "Symmetric"
	default:
		return "Unknown"
	}
}

// CanPunch returns true if at least one side is Cone (or has public IP).
func CanPunch(a, b NATType) bool {
	return a == NATNone || b == NATNone || a == NATCone || b == NATCone
}

// ─── Header ───

type Header struct {
	DataLen  uint32
	MainType uint16
	SubType  uint16
}

// ─── Encode / Decode ───

// Encode packs header + JSON payload into a byte slice.
func Encode(mainType, subType uint16, payload interface{}) ([]byte, error) {
	var jsonData []byte
	if payload != nil {
		var err error
		jsonData, err = json.Marshal(payload)
		if err != nil {
			return nil, fmt.Errorf("marshal payload: %w", err)
		}
	}
	h := Header{
		DataLen:  uint32(HeaderSize + len(jsonData)),
		MainType: mainType,
		SubType:  subType,
	}
	buf := new(bytes.Buffer)
	buf.Grow(int(h.DataLen))
	if err := binary.Write(buf, binary.LittleEndian, h); err != nil {
		return nil, err
	}
	buf.Write(jsonData)
	return buf.Bytes(), nil
}

// EncodeRaw packs header + raw (binary) payload.
func EncodeRaw(mainType, subType uint16, payload []byte) []byte {
	h := Header{
		DataLen:  uint32(HeaderSize + len(payload)),
		MainType: mainType,
		SubType:  subType,
	}
	buf := new(bytes.Buffer)
	buf.Grow(int(h.DataLen))
	_ = binary.Write(buf, binary.LittleEndian, h)
	buf.Write(payload)
	return buf.Bytes()
}

// DecodeHeader reads the 8-byte header from r.
func DecodeHeader(data []byte) (Header, error) {
	if len(data) < HeaderSize {
		return Header{}, io.ErrShortBuffer
	}
	var h Header
	err := binary.Read(bytes.NewReader(data[:HeaderSize]), binary.LittleEndian, &h)
	return h, err
}

// DecodePayload unmarshals the JSON portion after the header.
func DecodePayload(data []byte, v interface{}) error {
	if len(data) <= HeaderSize {
		return nil // empty payload is valid
	}
	return json.Unmarshal(data[HeaderSize:], v)
}

// ─── Common message structs ───

// LoginReq is sent by client on WSS connect.
type LoginReq struct {
	Node           string  `json:"node"`
	Token          uint64  `json:"token"`
	NodeSecret     string  `json:"nodeSecret,omitempty"`
	User           string  `json:"user,omitempty"`
	Version        string  `json:"version"`
	NATType        NATType `json:"natType"`
	ShareBandwidth int     `json:"shareBandwidth"`
	RelayEnabled   bool    `json:"relayEnabled"` // --relay flag
	SuperRelay     bool    `json:"superRelay"`   // --super flag
	PublicIP       string  `json:"publicIP,omitempty"`
	PublicPort     int     `json:"publicPort,omitempty"`
}

type LoginRsp struct {
	Error  int    `json:"error"`
	Detail string `json:"detail,omitempty"`
	Ts     int64  `json:"ts"`
	Token  uint64 `json:"token"`
	User   string `json:"user"`
	Node   string `json:"node"`
}

// ReportBasic is the initial system info report after login.
type ReportBasic struct {
	OS              string `json:"os"`
	Mac             string `json:"mac"`
	LanIP           string `json:"lanIP"`
	Version         string `json:"version"`
	HasIPv4         int    `json:"hasIPv4"`
	HasUPNPorNATPMP int    `json:"hasUPNPorNATPMP"`
	IPv6            string `json:"IPv6,omitempty"`
	PublicIP        string `json:"publicIP,omitempty"`
	PublicPort      int    `json:"publicPort,omitempty"`
}

type ReportBasicRsp struct {
	Error int `json:"error"`
}

// PunchParams carries the information needed for hole-punching.
type PunchParams struct {
	IP       string  `json:"ip"`
	Port     int     `json:"port"`
	NATType  NATType `json:"natType"`
	Token    uint64  `json:"token"` // TOTP for auth
	IPv6     string  `json:"ipv6,omitempty"`
	HasIPv4  int     `json:"hasIPv4"`
	LinkMode string  `json:"linkMode"` // "udp" or "tcp"
}

// ConnectReq is pushed by server to coordinate a connection.
type ConnectReq struct {
	From     string      `json:"from"`
	To       string      `json:"to"`
	FromIP   string      `json:"fromIP"`
	Peer     PunchParams `json:"peer"`
	AppName  string      `json:"appName,omitempty"`
	Protocol string      `json:"protocol"` // "tcp" or "udp"
	SrcPort  int         `json:"srcPort"`
	DstHost  string      `json:"dstHost"`
	DstPort  int         `json:"dstPort"`
}

type ConnectRsp struct {
	Error  int         `json:"error"`
	Detail string      `json:"detail,omitempty"`
	From   string      `json:"from"`
	To     string      `json:"to"`
	Peer   PunchParams `json:"peer,omitempty"`
}

// RelayNodeReq asks the server for a relay node.
type RelayNodeReq struct {
	PeerNode string `json:"peerNode"`
}

type RelayNodeRsp struct {
	RelayName  string `json:"relayName"`
	RelayIP    string `json:"relayIP"`
	RelayPort  int    `json:"relayPort"`
	RelayToken uint64 `json:"relayToken"`
	Mode       string `json:"mode"` // "private", "super", "server"
	Error      int    `json:"error"`
}

// AppConfig defines a tunnel application.
type AppConfig struct {
	AppName   string `json:"appName"`
	Protocol  string `json:"protocol"` // "tcp" or "udp"
	SrcPort   int    `json:"srcPort"`
	PeerNode  string `json:"peerNode"`
	DstHost   string `json:"dstHost"`
	DstPort   int    `json:"dstPort"`
	Enabled   int    `json:"enabled"`
	RelayNode string `json:"relayNode,omitempty"` // force specific relay
}

type SDWANNode struct {
	Node string `json:"node"`
	IP   string `json:"ip"`
}

type SDWANConfig struct {
	Enabled     bool        `json:"enabled,omitempty"`
	Name        string      `json:"name,omitempty"`
	GatewayCIDR string      `json:"gatewayCIDR"`
	Mode        string      `json:"mode,omitempty"` // hub | mesh | fullmesh
	IP          string      `json:"ip,omitempty"`   // node self IP if pushed per-node
	MTU         int         `json:"mtu,omitempty"`
	Routes      []string    `json:"routes,omitempty"`
	Nodes       []SDWANNode `json:"nodes"`
	UpdatedAt   int64       `json:"updatedAt,omitempty"`
}

type SDWANPeer struct {
	Node   string `json:"node"`
	IP     string `json:"ip"`
	Online bool   `json:"online"`
}

type SDWANPacket struct {
	FromNode string `json:"fromNode,omitempty"`
	ToNode   string `json:"toNode,omitempty"`
	SrcIP    string `json:"srcIP,omitempty"`
	DstIP    string `json:"dstIP,omitempty"`
	Payload  []byte `json:"payload"`
}

// ReportConnect is the connection result reported to server.
type ReportConnect struct {
	PeerNode       string  `json:"peerNode"`
	NATType        NATType `json:"natType"`
	PeerNATType    NATType `json:"peerNatType"`
	LinkMode       string  `json:"linkMode"` // "udppunch", "tcppunch", "relay"
	Error          string  `json:"error,omitempty"`
	RTT            int     `json:"rtt,omitempty"` // milliseconds
	RelayNode      string  `json:"relayNode,omitempty"`
	Protocol       string  `json:"protocol,omitempty"`
	SrcPort        int     `json:"srcPort,omitempty"`
	DstPort        int     `json:"dstPort,omitempty"`
	DstHost        string  `json:"dstHost,omitempty"`
	Version        string  `json:"version,omitempty"`
	ShareBandwidth int     `json:"shareBandWidth,omitempty"`
}
