package network

import (
	"context"
	"fmt"
	"net/http"
	"sync/atomic"

	"github.com/gorilla/websocket"

	"a301_game_server/config"
	"a301_game_server/pkg/logger"
)

// Server listens for WebSocket connections and creates Connection objects.
type Server struct {
	cfg      *config.Config
	upgrader websocket.Upgrader
	handler  PacketHandler
	nextID   atomic.Uint64
	srv      *http.Server
}

// NewServer creates a new WebSocket server.
func NewServer(cfg *config.Config, handler PacketHandler) *Server {
	return &Server{
		cfg:     cfg,
		handler: handler,
		upgrader: websocket.Upgrader{
			ReadBufferSize:  cfg.Network.ReadBufferSize,
			WriteBufferSize: cfg.Network.WriteBufferSize,
			CheckOrigin:     func(r *http.Request) bool { return true },
		},
	}
}

// Start begins listening for connections. Blocks until the context is cancelled.
func (s *Server) Start(ctx context.Context) error {
	mux := http.NewServeMux()
	mux.HandleFunc("/ws", s.handleWebSocket)
	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
	})

	addr := s.cfg.Server.Address()
	s.srv = &http.Server{
		Addr:    addr,
		Handler: mux,
	}

	errCh := make(chan error, 1)
	go func() {
		logger.Info("websocket server starting", "address", addr)
		if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			errCh <- fmt.Errorf("listen: %w", err)
		}
	}()

	select {
	case err := <-errCh:
		return err
	case <-ctx.Done():
		logger.Info("shutting down websocket server")
		return s.srv.Shutdown(context.Background())
	}
}

func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
	ws, err := s.upgrader.Upgrade(w, r, nil)
	if err != nil {
		logger.Error("websocket upgrade failed", "error", err, "remote", r.RemoteAddr)
		return
	}

	connID := s.nextID.Add(1)
	conn := NewConnection(
		connID,
		ws,
		s.handler,
		s.cfg.Network.SendChannelSize,
		s.cfg.Network.MaxMessageSize,
		s.cfg.Network.HeartbeatInterval,
		s.cfg.Network.HeartbeatTimeout,
	)

	logger.Info("client connected", "connID", connID, "remote", r.RemoteAddr)
	conn.Start()
}
