package proxy

import (
	"context"
	"log"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/wormhole/wormhole/internal/auth"
	"github.com/wormhole/wormhole/internal/store"
)

type httpTunnelServer struct {
	manager *Manager
	tunnel  *store.Tunnel
	srv     *http.Server
	stop    chan struct{}
	wg      sync.WaitGroup
}

func newHTTPTunnelServer(m *Manager, t *store.Tunnel) Service {
	return &httpTunnelServer{
		manager: m,
		tunnel:  t,
		stop:    make(chan struct{}),
	}
}

func (s *httpTunnelServer) ID() uint { return s.tunnel.ID }

func (s *httpTunnelServer) Start() error {
	addr := net.JoinHostPort("0.0.0.0", itoa(s.tunnel.ListenPort))
	s.srv = &http.Server{
		Addr:    addr,
		Handler: http.HandlerFunc(s.handleHTTP),
	}
	log.Printf("[proxy] http tunnel %d (visitor auth) listening on %s -> %s", s.tunnel.ID, addr, s.tunnel.TargetAddr)
	s.wg.Add(1)
	go func() {
		defer s.wg.Done()
		if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Printf("[proxy] http tunnel %d error: %v", s.tunnel.ID, err)
		}
	}()
	return nil
}

func (s *httpTunnelServer) handleHTTP(w http.ResponseWriter, r *http.Request) {
	t := s.tunnel
	ip := auth.ExtractRemoteIP(r)
	directIP := auth.DirectRemoteIP(r)
	s.manager.metrics.RecordRequestIP("tunnel", t.ID, ip, directIP)
	if !s.manager.security.Allow(directIP, t.ClientID, "tunnel", t.ID) {
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}
	if !s.manager.resAuth.CheckHTTP(w, r, "tunnel", t.ID) {
		return
	}
	s.manager.proxyTunnelHTTP(w, r, t)
}

func (s *httpTunnelServer) Stop() error {
	close(s.stop)
	if s.srv != nil {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		_ = s.srv.Shutdown(ctx)
	}
	s.wg.Wait()
	return nil
}
