package upstream
import (
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
	"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
)
// ProxyErrorHandler is a function that will be used to render error pages when
// HTTP proxies fail to connect to upstream servers.
type ProxyErrorHandler func(http.ResponseWriter, *http.Request, error)
// NewProxy creates a new multiUpstreamProxy that can serve requests directed to
// multiple upstreams.
func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, errorHandler ProxyErrorHandler) (http.Handler, error) {
	m := &multiUpstreamProxy{
		serveMux: http.NewServeMux(),
	}
	for _, upstream := range upstreams {
		if upstream.Static {
			m.registerStaticResponseHandler(upstream)
			continue
		}
		u, err := url.Parse(upstream.URI)
		if err != nil {
			return nil, fmt.Errorf("error parsing URI for upstream %q: %w", upstream.ID, err)
		}
		switch u.Scheme {
		case fileScheme:
			m.registerFileServer(upstream, u)
		case httpScheme, httpsScheme:
			m.registerHTTPUpstreamProxy(upstream, u, sigData, errorHandler)
		default:
			return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
		}
	}
	return m, nil
}
// multiUpstreamProxy will serve requests directed to multiple upstream servers
// registered in the serverMux.
type multiUpstreamProxy struct {
	serveMux *http.ServeMux
}
// ServerHTTP handles HTTP requests.
func (m *multiUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	m.serveMux.ServeHTTP(rw, req)
}
// registerStaticResponseHandler registers a static response handler with at the given path.
func (m *multiUpstreamProxy) registerStaticResponseHandler(upstream options.Upstream) {
	m.serveMux.Handle(upstream.Path, newStaticResponseHandler(upstream.ID, upstream.StaticCode))
}
// registerFileServer registers a new fileServer based on the configuration given.
func (m *multiUpstreamProxy) registerFileServer(upstream options.Upstream, u *url.URL) {
	logger.Printf("mapping path %q => file system %q", upstream.Path, u.Path)
	m.serveMux.Handle(upstream.Path, newFileServer(upstream.ID, upstream.Path, u.Path))
}
// registerHTTPUpstreamProxy registers a new httpUpstreamProxy based on the configuration given.
func (m *multiUpstreamProxy) registerHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) {
	logger.Printf("mapping path %q => upstream %q", upstream.Path, upstream.URI)
	m.serveMux.Handle(upstream.Path, newHTTPUpstreamProxy(upstream, u, sigData, errorHandler))
}
// NewProxyErrorHandler creates a ProxyErrorHandler using the template given.
func NewProxyErrorHandler(errorTemplate *template.Template, proxyPrefix string) ProxyErrorHandler {
	return func(rw http.ResponseWriter, req *http.Request, proxyErr error) {
		logger.Printf("Error proxying to upstream server: %v", proxyErr)
		rw.WriteHeader(http.StatusBadGateway)
		data := struct {
			Title       string
			Message     string
			ProxyPrefix string
		}{
			Title:       "Bad Gateway",
			Message:     "Error proxying to upstream server",
			ProxyPrefix: proxyPrefix,
		}
		errorTemplate.Execute(rw, data)
	}
}