package gateway

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/pingcap-inc/nexuscrm-api-host/processhttp"
)

const downstreamWriteHeadroom = 5 * time.Second

// ServerConfig owns edge TLS and bounded process lifecycle settings.
type ServerConfig struct {
	ListenAddress   string
	TLSCertFile     string
	TLSKeyFile      string
	ShutdownTimeout time.Duration
}

// Server is the lifecycle owner that can observe complete HTTP transport
// shutdown. Gateway admission intentionally does not pretend to own it.
type Server struct {
	gateway         *Gateway
	server          *http.Server
	tlsCertFile     string
	tlsKeyFile      string
	shutdownTimeout time.Duration
}

func NewServer(gateway *Gateway, config ServerConfig) (*Server, error) {
	if gateway == nil {
		return nil, errors.New("gateway: handler is required")
	}
	if strings.TrimSpace(config.ListenAddress) == "" || config.ListenAddress != strings.TrimSpace(config.ListenAddress) {
		return nil, errors.New("gateway: listen address is required")
	}
	if strings.TrimSpace(config.TLSCertFile) == "" || strings.TrimSpace(config.TLSKeyFile) == "" {
		return nil, errors.New("gateway: TLS certificate and key files are required")
	}
	minimumShutdownTimeout := gateway.maximumRouteTimeout() + downstreamWriteHeadroom
	if config.ShutdownTimeout == 0 {
		config.ShutdownTimeout = minimumShutdownTimeout
	}
	if config.ShutdownTimeout < minimumShutdownTimeout || config.ShutdownTimeout > time.Minute {
		return nil, fmt.Errorf("gateway: shutdown timeout must be between %s and 1m for this route generation", minimumShutdownTimeout)
	}
	return &Server{
		gateway: gateway,
		server: &http.Server{
			Addr: config.ListenAddress,
			// Gateway owns the public rejection grammar for upgrades. The
			// process layer only removes the hijack capability so neither the
			// proxy nor a future handler can escape http.Server.Shutdown.
			Handler:           processhttp.WithoutHijacker(gateway),
			ReadHeaderTimeout: 5 * time.Second,
			// The route deadline bounds upstream work. WriteTimeout additionally
			// bounds the client-facing transport phase so a slow reader cannot
			// retain a finite Gateway admission slot indefinitely.
			WriteTimeout:   minimumShutdownTimeout,
			IdleTimeout:    60 * time.Second,
			MaxHeaderBytes: 64 << 10,
			TLSConfig: &tls.Config{
				MinVersion: tls.VersionTLS12,
			},
		},
		tlsCertFile:     strings.TrimSpace(config.TLSCertFile),
		tlsKeyFile:      strings.TrimSpace(config.TLSKeyFile),
		shutdownTimeout: config.ShutdownTimeout,
	}, nil
}

// Run serves TLS until the caller cancels or the listener fails. Cancellation
// first closes readiness/admission, then waits for the owning HTTP server to
// finish complete responses, and only then closes liveness.
func (s *Server) Run(ctx context.Context) error {
	if ctx == nil {
		return errors.New("gateway: run context is required")
	}
	listener, err := net.Listen("tcp", s.server.Addr)
	if err != nil {
		s.gateway.Close()
		return fmt.Errorf("gateway: listen: %w", err)
	}
	return s.runOnListener(ctx, listener)
}

func (s *Server) runOnListener(ctx context.Context, listener net.Listener) error {
	if ctx == nil || listener == nil {
		return errors.New("gateway: run context and listener are required")
	}
	serveErr := make(chan error, 1)
	go func() {
		serveErr <- s.server.ServeTLS(listener, s.tlsCertFile, s.tlsKeyFile)
	}()
	select {
	case err := <-serveErr:
		s.gateway.Close()
		if errors.Is(err, http.ErrServerClosed) {
			return nil
		}
		return fmt.Errorf("gateway: serve TLS: %w", err)
	case <-ctx.Done():
		s.gateway.Drain()
		shutdownCtx, cancel := context.WithTimeout(context.Background(), s.shutdownTimeout)
		shutdownErr := s.server.Shutdown(shutdownCtx)
		cancel()
		if shutdownErr != nil {
			closeErr := s.server.Close()
			s.gateway.Close()
			return errors.Join(fmt.Errorf("gateway: graceful shutdown: %w", shutdownErr), closeErr)
		}
		s.gateway.Close()
		err := <-serveErr
		if err != nil && !errors.Is(err, http.ErrServerClosed) {
			return fmt.Errorf("gateway: serve TLS after shutdown: %w", err)
		}
		return nil
	}
}
