package factory

import (
	"fmt"
	"net"
	"net/http"

	portainer "github.com/portainer/portainer/api"
	"github.com/portainer/portainer/api/crypto"
	"github.com/portainer/portainer/api/http/proxy/factory/agent"
	"github.com/portainer/portainer/api/internal/endpointutils"
	"github.com/portainer/portainer/api/url"
	"github.com/portainer/portainer/pkg/libhttp/ssrf"

	"github.com/pkg/errors"
	"github.com/rs/zerolog/log"
)

// ProxyServer provide an extended proxy with a local server to forward requests
type ProxyServer struct {
	server *http.Server
	Port   int
}

// NewAgentProxy creates a new instance of ProxyServer that wrap http requests with agent headers
func (factory *ProxyFactory) NewAgentProxy(endpoint *portainer.Endpoint) (*ProxyServer, error) {
	urlString := endpoint.URL

	if endpointutils.IsEdgeEndpoint(endpoint) {
		tunnelAddr, err := factory.reverseTunnelService.TunnelAddr(endpoint)
		if err != nil {
			return nil, errors.Wrap(err, "failed starting tunnel")
		}

		urlString = "http://" + tunnelAddr
	}

	endpointURL, err := url.ParseURL(urlString)
	if err != nil {
		return nil, errors.Wrapf(err, "failed parsing url %s", endpoint.URL)
	}

	endpointURL.Scheme = "http"

	var innerTransport *http.Transport
	if endpoint.TLSConfig.TLS || endpoint.TLSConfig.TLSSkipVerify {
		tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig)
		if err != nil {
			return nil, errors.WithMessage(err, "failed generating tls configuration")
		}

		endpointURL.Scheme = "https"

		if endpointutils.IsEdgeEndpoint(endpoint) {
			innerTransport = ssrf.NewInternalTransport(tlsConfig)
		} else {
			innerTransport = ssrf.NewTransport(tlsConfig)
			innerTransport.Protocols = ssrf.HTTP1Only()
		}
	} else if endpointutils.IsEdgeEndpoint(endpoint) {
		innerTransport = ssrf.NewInternalTransport(nil)
	} else {
		innerTransport = ssrf.NewTransport(nil)
	}

	proxy := NewSingleHostReverseProxyWithHostHeader(endpointURL)

	proxy.Transport = agent.NewTransport(factory.signatureService, innerTransport)

	proxyServer := &ProxyServer{
		server: &http.Server{
			Handler: proxy,
		},
		Port: 0,
	}

	if err := proxyServer.start(); err != nil {
		return nil, errors.Wrap(err, "failed starting proxy server")
	}

	return proxyServer, nil
}

func (proxy *ProxyServer) start() error {
	listener, err := net.Listen("tcp", ":0")
	if err != nil {
		return err
	}

	proxy.Port = listener.Addr().(*net.TCPAddr).Port

	go func() {
		proxyHost := fmt.Sprintf("127.0.0.1:%d", proxy.Port)
		log.Debug().Str("host", proxyHost).Msg("starting proxy server")

		err := proxy.server.Serve(listener)
		log.Debug().Str("host", proxyHost).Msg("exiting proxy server")

		if err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Debug().Str("host", proxyHost).Err(err).Msg("proxy server exited with an error")
		}
	}()

	return nil
}

// Close shuts down the server
func (proxy *ProxyServer) Close() {
	if proxy.server != nil {
		if err := proxy.server.Close(); err != nil {
			log.Warn().Err(err).Msg("failed to close proxy server")
		}
	}
}
