package pluginproxy

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/util"
)

// ApplyRoute should use the plugin route data to set auth headers and custom headers.
func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route *plugins.AppPluginRoute,
	ds *models.DataSource) {
	proxyPath = strings.TrimPrefix(proxyPath, route.Path)

	data := templateData{
		JsonData:       ds.JsonData.Interface().(map[string]interface{}),
		SecureJsonData: ds.SecureJsonData.Decrypt(),
	}

	if len(route.URL) > 0 {
		interpolatedURL, err := interpolateString(route.URL, data)
		if err != nil {
			logger.Error("Error interpolating proxy url", "error", err)
			return
		}

		routeURL, err := url.Parse(interpolatedURL)
		if err != nil {
			logger.Error("Error parsing plugin route url", "error", err)
			return
		}

		req.URL.Scheme = routeURL.Scheme
		req.URL.Host = routeURL.Host
		req.Host = routeURL.Host
		req.URL.Path = util.JoinURLFragments(routeURL.Path, proxyPath)
	}

	if err := addQueryString(req, route, data); err != nil {
		logger.Error("Failed to render plugin URL query string", "error", err)
	}

	if err := addHeaders(&req.Header, route, data); err != nil {
		logger.Error("Failed to render plugin headers", "error", err)
	}

	if err := setBodyContent(req, route, data); err != nil {
		logger.Error("Failed to set plugin route body content", "error", err)
	}

	if tokenProvider, err := getTokenProvider(ctx, ds, route, data); err != nil {
		logger.Error("Failed to resolve auth token provider", "error", err)
	} else if tokenProvider != nil {
		if token, err := tokenProvider.getAccessToken(); err != nil {
			logger.Error("Failed to get access token", "error", err)
		} else {
			req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
		}
	}

	logger.Info("Requesting", "url", req.URL.String())
}

func getTokenProvider(ctx context.Context, ds *models.DataSource, pluginRoute *plugins.AppPluginRoute,
	data templateData) (accessTokenProvider, error) {
	authType := pluginRoute.AuthType

	// Plugin can override authentication type specified in route configuration
	if authTypeOverride := ds.JsonData.Get("authenticationType").MustString(); authTypeOverride != "" {
		authType = authTypeOverride
	}

	tokenAuth, err := interpolateAuthParams(pluginRoute.TokenAuth, data)
	if err != nil {
		return nil, err
	}
	jwtTokenAuth, err := interpolateAuthParams(pluginRoute.JwtTokenAuth, data)
	if err != nil {
		return nil, err
	}

	switch authType {
	case "gce":
		if jwtTokenAuth == nil {
			return nil, fmt.Errorf("'jwtTokenAuth' not configured for authentication type '%s'", authType)
		}
		provider := newGceAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth)
		return provider, nil

	case "jwt":
		if jwtTokenAuth == nil {
			return nil, fmt.Errorf("'jwtTokenAuth' not configured for authentication type '%s'", authType)
		}
		provider := newJwtAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth)
		return provider, nil

	case "":
		// Fallback to authentication methods when authentication type isn't explicitly configured
		if tokenAuth != nil {
			provider := newGenericAccessTokenProvider(ds, pluginRoute, tokenAuth)
			return provider, nil
		}
		if jwtTokenAuth != nil {
			provider := newJwtAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth)
			return provider, nil
		}

		// No authentication
		return nil, nil

	default:
		return nil, fmt.Errorf("authentication type '%s' not supported", authType)
	}
}

func interpolateAuthParams(tokenAuth *plugins.JwtTokenAuth, data templateData) (*plugins.JwtTokenAuth, error) {
	if tokenAuth == nil {
		// Nothing to interpolate
		return nil, nil
	}

	interpolatedUrl, err := interpolateString(tokenAuth.Url, data)
	if err != nil {
		return nil, err
	}

	interpolatedParams := make(map[string]string)
	for key, value := range tokenAuth.Params {
		interpolatedParam, err := interpolateString(value, data)
		if err != nil {
			return nil, err
		}
		interpolatedParams[key] = interpolatedParam
	}

	return &plugins.JwtTokenAuth{
		Url:    interpolatedUrl,
		Scopes: tokenAuth.Scopes,
		Params: interpolatedParams,
	}, nil
}
