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/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"
	httpTransport := &http.Transport{}

	if endpoint.TLSConfig.TLS || endpoint.TLSConfig.TLSSkipVerify {
		config, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
		if err != nil {
			return nil, errors.WithMessage(err, "failed generating tls configuration")
		}

		httpTransport.TLSClientConfig = config
		endpointURL.Scheme = "https"
	}

	proxy := newSingleHostReverseProxyWithHostHeader(endpointURL)

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

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

	err = proxyServer.start()
	if 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 {
		proxy.server.Close()
	}
}
