// inp2ps — INP2P Signaling Server
package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/openp2p-cn/inp2p/internal/server"
	"github.com/openp2p-cn/inp2p/pkg/auth"
	"github.com/openp2p-cn/inp2p/pkg/config"
	"github.com/openp2p-cn/inp2p/pkg/nat"
	"github.com/openp2p-cn/inp2p/pkg/protocol"
)

func main() {
	cfg := config.DefaultServerConfig()

	flag.IntVar(&cfg.WSPort, "ws-port", cfg.WSPort, "WebSocket signaling port")
	flag.IntVar(&cfg.WebPort, "web-port", cfg.WebPort, "Web console port")
	flag.IntVar(&cfg.STUNUDP1, "stun-udp1", cfg.STUNUDP1, "UDP STUN port 1")
	flag.IntVar(&cfg.STUNUDP2, "stun-udp2", cfg.STUNUDP2, "UDP STUN port 2")
	flag.IntVar(&cfg.STUNTCP1, "stun-tcp1", cfg.STUNTCP1, "TCP STUN port 1")
	flag.IntVar(&cfg.STUNTCP2, "stun-tcp2", cfg.STUNTCP2, "TCP STUN port 2")
	flag.StringVar(&cfg.DBPath, "db", cfg.DBPath, "SQLite database path")
	flag.StringVar(&cfg.CertFile, "cert", "", "TLS certificate file")
	flag.StringVar(&cfg.KeyFile, "key", "", "TLS key file")
	flag.IntVar(&cfg.LogLevel, "log-level", cfg.LogLevel, "Log level (0=debug 1=info 2=warn 3=error)")
	token := flag.Uint64("token", 0, "Master authentication token (uint64)")
	user := flag.String("user", "", "Username for token generation (requires -password)")
	pass := flag.String("password", "", "Password for token generation")
	version := flag.Bool("version", false, "Print version and exit")
	flag.Parse()

	if *version {
		fmt.Printf("inp2ps version %s\ncommit: %s\nbuild: %s\ngo: %s\n",
			config.Version, config.GitCommit, config.BuildTime, config.GoVersion)
		os.Exit(0)
	}

	// Token: either direct value or generated from user+password
	if *token > 0 {
		cfg.Token = *token
	} else if *user != "" && *pass != "" {
		cfg.Token = auth.MakeToken(*user, *pass)
		log.Printf("[main] token generated from credentials: %d", cfg.Token)
	}

	cfg.FillFromEnv()

	if err := cfg.Validate(); err != nil {
		log.Fatalf("[main] config error: %v", err)
	}

	log.Printf("[main] inp2ps v%s starting", config.Version)
	log.Printf("[main] WSS :%d | STUN UDP :%d,%d | STUN TCP :%d,%d",
		cfg.WSPort, cfg.STUNUDP1, cfg.STUNUDP2, cfg.STUNTCP1, cfg.STUNTCP2)

	// ─── STUN Servers ───
	stunQuit := make(chan struct{})

	startSTUN := func(proto string, port int, fn func(int, <-chan struct{}) error) {
		go func() {
			log.Printf("[main] %s STUN listening on :%d", proto, port)
			if err := fn(port, stunQuit); err != nil {
				log.Printf("[main] %s STUN :%d error: %v", proto, port, err)
			}
		}()
	}

	startSTUN("UDP", cfg.STUNUDP1, nat.ServeUDPSTUN)
	if cfg.STUNUDP2 != cfg.STUNUDP1 {
		startSTUN("UDP", cfg.STUNUDP2, nat.ServeUDPSTUN)
	}
	startSTUN("TCP", cfg.STUNTCP1, nat.ServeTCPSTUN)
	if cfg.STUNTCP2 != cfg.STUNTCP1 {
		startSTUN("TCP", cfg.STUNTCP2, nat.ServeTCPSTUN)
	}

	// ─── Signaling Server ───
	// ─── Signaling Server ───
	srv := server.New(cfg)
	srv.StartCleanup()

	// Admin-only Middleware
	adminMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			if r.URL.Path == "/api/v1/auth/login" {
				next(w, r)
				return
			}
			authHeader := r.Header.Get("Authorization")
			valid := authHeader == fmt.Sprintf("Bearer %d", cfg.Token)
			if !valid {
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusUnauthorized)
				fmt.Fprintf(w, `{"error":401,"message":"unauthorized"}`)
				return
			}
			next(w, r)
		}
	}

	// Tenant or Admin Middleware
	tenantMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {
			if r.URL.Path == "/api/v1/auth/login" {
				next(w, r)
				return
			}
			authHeader := r.Header.Get("Authorization")
			if authHeader == fmt.Sprintf("Bearer %d", cfg.Token) {
				next(w, r)
				return
			}
			// check API key
			if srv.Store() != nil {
				if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
					next(w, r)
					return
				}
			}
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusUnauthorized)
			fmt.Fprintf(w, `{"error":401,"message":"unauthorized"}`)
			return
		}
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/ws", srv.HandleWS)

	// Serve Static Web Console
	webDir := "/root/.openclaw/workspace/inp2p/web"
	mux.Handle("/", http.FileServer(http.Dir(webDir)))

	// Tenant APIs (API key auth inside handlers)
	mux.HandleFunc("/api/v1/admin/tenants", adminMiddleware(srv.HandleAdminCreateTenant))
	mux.HandleFunc("/api/v1/admin/tenants/", adminMiddleware(srv.HandleAdminCreateAPIKey))
	mux.HandleFunc("/api/v1/tenants/enroll", srv.HandleTenantEnroll)
	mux.HandleFunc("/api/v1/enroll/consume", srv.HandleEnrollConsume)

	mux.HandleFunc("/api/v1/auth/login", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		var req struct {
			Token uint64 `json:"token,string"` // support string from frontend
		}
		// Try string first then uint64
		body, _ := io.ReadAll(r.Body)
		if err := json.Unmarshal(body, &req); err != nil {
			var req2 struct {
				Token uint64 `json:"token"`
			}
			if err := json.Unmarshal(body, &req2); err != nil {
				http.Error(w, "bad request", http.StatusBadRequest)
				return
			}
			req.Token = req2.Token
		}

		valid := req.Token == cfg.Token
		if !valid {
			for _, t := range cfg.Tokens {
				if req.Token == t {
					valid = true
					break
				}
			}
		}
		if !valid {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusUnauthorized)
			fmt.Fprintf(w, `{"error":1,"message":"invalid token"}`)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintf(w, `{"error":0,"token":"%d"}`, cfg.Token)
	})

	mux.HandleFunc("/api/v1/health", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintf(w, `{"status":"ok","version":"%s","nodes":%d}`, config.Version, len(srv.GetOnlineNodes()))
	}))

	mux.HandleFunc("/api/v1/nodes", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		// tenant filter by API key
		tenantID := int64(0)
		if srv.Store() != nil {
			if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
				tenantID = ten.ID
			}
		}
		if tenantID > 0 {
			nodes := srv.GetOnlineNodesByTenant(tenantID)
			_ = json.NewEncoder(w).Encode(map[string]any{"nodes": nodes})
			return
		}
		nodes := srv.GetOnlineNodes()
		_ = json.NewEncoder(w).Encode(map[string]any{"nodes": nodes})
	}))

	mux.HandleFunc("/api/v1/sdwans", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		// tenant filter by API key
		tenantID := int64(0)
		if srv.Store() != nil {
			if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
				tenantID = ten.ID
			}
		}
		if tenantID > 0 {
			_ = json.NewEncoder(w).Encode(srv.GetSDWANTenant(tenantID))
			return
		}
		_ = json.NewEncoder(w).Encode(srv.GetSDWAN())
	}))

	mux.HandleFunc("/api/v1/sdwan/edit", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		var req protocol.SDWANConfig
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		// tenant filter by API key
		tenantID := int64(0)
		if srv.Store() != nil {
			if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
				tenantID = ten.ID
			}
		}
		if tenantID > 0 {
			if err := srv.SetSDWANTenant(tenantID, req); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			w.Header().Set("Content-Type", "application/json")
			_ = json.NewEncoder(w).Encode(map[string]any{"error": 0, "message": "ok"})
			return
		}
		if err := srv.SetSDWAN(req); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{"error": 0, "message": "ok"})
	}))

	// Remote Config Push API
	mux.HandleFunc("/api/v1/nodes/apps", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		var req struct {
			Node string               `json:"node"`
			Apps []protocol.AppConfig `json:"apps"`
		}
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		node := srv.GetNode(req.Node)
		if node == nil {
			http.Error(w, "node not found", http.StatusNotFound)
			return
		}
		// tenant filter by API key
		tenantID := int64(0)
		if srv.Store() != nil {
			if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
				tenantID = ten.ID
			}
		}
		if tenantID > 0 && node.TenantID != tenantID {
			http.Error(w, "node not found", http.StatusNotFound)
			return
		}
		// Push to client
		_ = node.Conn.Write(protocol.MsgPush, protocol.SubPushConfig, req.Apps)
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{"error": 0, "message": "config push sent"})
	}))

	// Kick (disconnect) a node
	mux.HandleFunc("/api/v1/nodes/kick", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		var req struct {
			Node string `json:"node"`
		}
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		node := srv.GetNode(req.Node)
		if node == nil {
			http.Error(w, "node not found or offline", http.StatusNotFound)
			return
		}
		// tenant filter by API key
		tenantID := int64(0)
		if srv.Store() != nil {
			if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
				tenantID = ten.ID
			}
		}
		if tenantID > 0 && node.TenantID != tenantID {
			http.Error(w, "node not found", http.StatusNotFound)
			return
		}
		node.Conn.Close()
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{"error": 0, "message": "node kicked"})
	}))

	// Trigger P2P connect between two nodes
	mux.HandleFunc("/api/v1/connect", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}
		var req struct {
			From    string `json:"from"`
			To      string `json:"to"`
			SrcPort int    `json:"srcPort"`
			DstPort int    `json:"dstPort"`
			AppName string `json:"appName"`
		}
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		fromNode := srv.GetNode(req.From)
		if fromNode == nil {
			http.Error(w, "source node offline", http.StatusNotFound)
			return
		}
		// tenant filter by API key
		tenantID := int64(0)
		if srv.Store() != nil {
			if ten, err := srv.Store().VerifyAPIKey(server.BearerToken(r)); err == nil && ten != nil {
				tenantID = ten.ID
			}
		}
		if tenantID > 0 && fromNode.TenantID != tenantID {
			http.Error(w, "node not found", http.StatusNotFound)
			return
		}
		app := protocol.AppConfig{
			AppName:  req.AppName,
			Protocol: "tcp",
			SrcPort:  req.SrcPort,
			PeerNode: req.To,
			DstHost:  "127.0.0.1",
			DstPort:  req.DstPort,
			Enabled:  1,
		}
		// enforce same-tenant target
		if tenantID > 0 {
			toNode := srv.GetNode(req.To)
			if toNode == nil || toNode.TenantID != tenantID {
				http.Error(w, "node not found", http.StatusNotFound)
				return
			}
		}
		if err := srv.PushConnect(fromNode, req.To, app); err != nil {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusBadGateway)
			_ = json.NewEncoder(w).Encode(map[string]any{"error": 1, "message": err.Error()})
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{"error": 0, "message": "connect request sent"})
	}))

	// Server uptime + detailed stats
	mux.HandleFunc("/api/v1/stats", tenantMiddleware(func(w http.ResponseWriter, r *http.Request) {
		nodes := srv.GetOnlineNodes()
		coneCount, symmCount, unknCount := 0, 0, 0
		relayCount := 0
		for _, n := range nodes {
			switch n.NATType {
			case 1:
				coneCount++
			case 2:
				symmCount++
			default:
				unknCount++
			}
			if n.RelayEnabled || n.SuperRelay {
				relayCount++
			}
		}
		sdwan := srv.GetSDWAN()
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{
			"nodes":     len(nodes),
			"relay":     relayCount,
			"cone":      coneCount,
			"symmetric": symmCount,
			"unknown":   unknCount,
			"sdwan":     sdwan.Enabled,
			"version":   config.Version,
		})
	}))

	// ─── HTTP Listener ───
	ln, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.WSPort))
	if err != nil {
		log.Fatalf("[main] listen :%d: %v", cfg.WSPort, err)
	}
	log.Printf("[main] signaling server on :%d (no TLS — use reverse proxy for production)", cfg.WSPort)

	// Enable TCP keepalive at server level
	httpSrv := &http.Server{
		Handler: mux,
		ReadHeaderTimeout: 10 * time.Second,
		WriteTimeout: 30 * time.Second,
		IdleTimeout: 120 * time.Second,
	}
	go func() {
		if err := httpSrv.Serve(ln); err != http.ErrServerClosed {
			log.Fatalf("[main] serve: %v", err)
		}
	}()

	// ─── Graceful Shutdown ───
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
	<-sigCh

	log.Println("[main] shutting down...")
	close(stunQuit)
	srv.Stop()
	httpSrv.Shutdown(context.Background())
	log.Println("[main] goodbye")
}
