package helpers

import (
	"net/http"
	"net/http/httputil"
	"net/url"

	"github.com/gruntwork-io/terragrunt/internal/tf/cliconfig"
	"github.com/gruntwork-io/terragrunt/pkg/log"
	svchost "github.com/hashicorp/terraform-svchost"
	"github.com/labstack/echo/v4"
)

type ReverseProxy struct {
	ServerURL   *url.URL
	CredsSource *cliconfig.CredentialsSource

	// Transport carries the injected vhttp client's RoundTripper so the
	// proxied data path honors the same virtualization and pooling as the
	// discovery requests. Required: NewRequest panics on nil rather than
	// letting httputil.ReverseProxy silently fall back to
	// http.DefaultTransport and reach the real network un-injected.
	Transport http.RoundTripper

	Rewrite        func(*httputil.ProxyRequest)
	ModifyResponse func(resp *http.Response) error
	ErrorHandler   func(http.ResponseWriter, *http.Request, error)

	Logger log.Logger
}

func (reverseProxy ReverseProxy) WithModifyResponse(
	fn func(resp *http.Response) error,
) *ReverseProxy {
	reverseProxy.ModifyResponse = fn
	return &reverseProxy
}

func (reverseProxy *ReverseProxy) NewRequest(ctx echo.Context, targetURL *url.URL) (er error) {
	if reverseProxy.Transport == nil {
		panic(
			"helpers.ReverseProxy: nil Transport; wire the vhttp client's transport at construction",
		)
	}

	proxy := &httputil.ReverseProxy{
		Transport: reverseProxy.Transport,
		Rewrite: func(req *httputil.ProxyRequest) {
			req.Out.Host = targetURL.Host
			req.Out.URL = targetURL

			if reverseProxy.CredsSource != nil {
				hostname := svchost.Hostname(req.Out.URL.Hostname())
				if creds := reverseProxy.CredsSource.ForHost(hostname); creds != nil {
					creds.PrepareRequest(req.Out)
				}
			}

			if reverseProxy.Rewrite != nil {
				reverseProxy.Rewrite(req)
			}
		},
		ModifyResponse: func(resp *http.Response) error {
			if reverseProxy.ModifyResponse != nil {
				return reverseProxy.ModifyResponse(resp)
			}

			return nil
		},
		ErrorHandler: func(resp http.ResponseWriter, req *http.Request, err error) {
			reverseProxy.Logger.Errorf(
				"remote %s unreachable, could not forward: %v",
				targetURL,
				err,
			)
			ctx.Error(echo.NewHTTPError(http.StatusServiceUnavailable))

			if reverseProxy.ErrorHandler != nil {
				reverseProxy.ErrorHandler(resp, req, err)
			}
		},
	}

	proxy.ServeHTTP(ctx.Response(), ctx.Request())

	return nil
}
