package pluginconfig

import (
	"context"
	"fmt"
	"os"
	"slices"
	"strconv"
	"strings"

	"github.com/grafana/grafana-aws-sdk/pkg/awsds"
	"github.com/grafana/grafana-azure-sdk-go/v2/azsettings"
	"github.com/grafana/grafana-plugin-sdk-go/backend/proxy"

	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/login/social"
	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/plugins/envvars"
	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsso"
)

var _ envvars.Provider = (*EnvVarsProvider)(nil)

type EnvVarsProvider struct {
	cfg         *PluginInstanceCfg
	license     plugins.Licensing
	logger      log.Logger
	ssoSettings pluginsso.SettingsProvider
}

func NewEnvVarsProvider(cfg *PluginInstanceCfg, license plugins.Licensing, ssoSettings pluginsso.SettingsProvider) *EnvVarsProvider {
	return &EnvVarsProvider{
		cfg:         cfg,
		license:     license,
		logger:      log.New("plugins.envvars"),
		ssoSettings: ssoSettings,
	}
}

func (p *EnvVarsProvider) PluginEnvVars(ctx context.Context, plugin *plugins.Plugin) []string {
	hostEnv := []string{
		p.envVar("GF_VERSION", p.cfg.GrafanaVersion),
	}

	if p.license != nil {
		hostEnv = append(
			hostEnv,
			p.envVar("GF_EDITION", p.license.Edition()),
			p.envVar("GF_ENTERPRISE_LICENSE_PATH", p.license.Path()),
			p.envVar("GF_ENTERPRISE_APP_URL", p.license.AppURL()),
		)
		hostEnv = append(hostEnv, p.license.Environment()...)
	}

	if plugin.ExternalService != nil {
		hostEnv = append(
			hostEnv,
			p.envVar("GF_APP_URL", p.cfg.GrafanaAppURL),
			p.envVar("GF_PLUGIN_APP_CLIENT_ID", plugin.ExternalService.ClientID),
			p.envVar("GF_PLUGIN_APP_CLIENT_SECRET", plugin.ExternalService.ClientSecret),
		)
		if plugin.ExternalService.PrivateKey != "" {
			hostEnv = append(hostEnv, p.envVar("GF_PLUGIN_APP_PRIVATE_KEY", plugin.ExternalService.PrivateKey))
		}
	}

	hostEnv = append(hostEnv, p.featureToggleEnableVars(ctx)...)
	hostEnv = append(hostEnv, p.awsEnvVars(plugin.PluginID())...)
	hostEnv = append(hostEnv, p.secureSocksProxyEnvVars()...)
	hostEnv = append(hostEnv, azsettings.WriteToEnvStr(p.getAzureSettings())...)
	hostEnv = append(hostEnv, p.tracingEnvVars(plugin)...)
	hostEnv = append(hostEnv, p.pluginSettingsEnvVars(plugin.PluginID())...)

	// If SkipHostEnvVars is enabled, get some allowed variables from the current process and pass
	// them down to the plugin. If the flag is not set, do not add anything else because ALL env vars
	// from the current process (os.Environ()) will be forwarded to the plugin's process by go-plugin
	if plugin.SkipHostEnvVars {
		hostEnv = append(hostEnv, envvars.PermittedHostEnvVars()...)
	}

	return hostEnv
}

func (p *EnvVarsProvider) featureToggleEnableVars(ctx context.Context) []string {
	var variables []string // an array is used for consistency and keep the logic simpler for no features case

	if p.cfg.Features == nil {
		return variables
	}

	enabledFeatures := p.cfg.Features.GetEnabled(ctx)
	if len(enabledFeatures) > 0 {
		features := make([]string, 0, len(enabledFeatures))
		for feat := range enabledFeatures {
			features = append(features, feat)
		}
		variables = append(variables, p.envVar("GF_INSTANCE_FEATURE_TOGGLES_ENABLE", strings.Join(features, ",")))
	}

	return variables
}

func (p *EnvVarsProvider) awsEnvVars(pluginID string) []string {
	if !slices.Contains[[]string, string](p.cfg.AWSForwardSettingsPlugins, pluginID) {
		return []string{}
	}

	var variables []string
	if !p.cfg.AWSAssumeRoleEnabled {
		variables = append(variables, p.envVar(awsds.AssumeRoleEnabledEnvVarKeyName, "false"))
	}
	if p.cfg.AWSPerDatasourceHTTPProxyEnabled {
		variables = append(variables, p.envVar(awsds.PerDatasourceHTTPProxyEnabledEnvVarKeyName, "true"))
	}
	if len(p.cfg.AWSAllowedAuthProviders) > 0 {
		variables = append(variables, p.envVar(awsds.AllowedAuthProvidersEnvVarKeyName, strings.Join(p.cfg.AWSAllowedAuthProviders, ",")))
	}
	if p.cfg.AWSExternalId != "" {
		variables = append(variables, p.envVar(awsds.GrafanaAssumeRoleExternalIdKeyName, p.cfg.AWSExternalId))
	}
	if p.cfg.AWSSessionDuration != "" {
		variables = append(variables, p.envVar(awsds.SessionDurationEnvVarKeyName, p.cfg.AWSSessionDuration))
	}
	if p.cfg.AWSListMetricsPageLimit != "" {
		variables = append(variables, p.envVar(awsds.ListMetricsPageLimitKeyName, p.cfg.AWSListMetricsPageLimit))
	}

	// Forward AWS SDK credential chain env vars from the host so that plugins can use
	// EKS IRSA, ECS task roles, and other environment-based credential providers.
	for _, envVarName := range awsHostEnvVarNames {
		if v, ok := os.LookupEnv(envVarName); ok {
			variables = append(variables, p.envVar(envVarName, v))
		}
	}

	return variables
}

// awsHostEnvVarNames are the host environment variables forwarded to AWS plugins.
// These are needed for the AWS SDK default credential chain to resolve credentials
// in container environments (EKS with IRSA, ECS Fargate).
var awsHostEnvVarNames = []string{
	// EKS IRSA
	"AWS_ROLE_ARN",
	"AWS_WEB_IDENTITY_TOKEN_FILE",
	// ECS (Fargate / EC2)
	"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
	"AWS_CONTAINER_CREDENTIALS_FULL_URI",
	"AWS_CONTAINER_AUTHORIZATION_TOKEN",
	"AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE",
	// Region
	"AWS_REGION",
	"AWS_DEFAULT_REGION",
}

func (p *EnvVarsProvider) secureSocksProxyEnvVars() []string {
	if p.cfg.ProxySettings.Enabled {
		return []string{
			// nolint:staticcheck
			p.envVar(proxy.PluginSecureSocksProxyClientCertFilePathEnvVarName, p.cfg.ProxySettings.ClientCertFilePath),
			// nolint:staticcheck
			p.envVar(proxy.PluginSecureSocksProxyClientKeyFilePathEnvVarName, p.cfg.ProxySettings.ClientKeyFilePath),
			// nolint:staticcheck
			p.envVar(proxy.PluginSecureSocksProxyRootCACertFilePathsEnvVarName, strings.Join(p.cfg.ProxySettings.RootCAFilePaths, " ")),
			// nolint:staticcheck
			p.envVar(proxy.PluginSecureSocksProxyAddressEnvVarName, p.cfg.ProxySettings.ProxyAddress),
			// nolint:staticcheck
			p.envVar(proxy.PluginSecureSocksProxyServerNameEnvVarName, p.cfg.ProxySettings.ServerName),
			// nolint:staticcheck
			p.envVar(proxy.PluginSecureSocksProxyEnabledEnvVarName, strconv.FormatBool(p.cfg.ProxySettings.Enabled)),
			// nolint:staticcheck
			p.envVar(proxy.PluginSecureSocksProxyAllowInsecureEnvVarName, strconv.FormatBool(p.cfg.ProxySettings.AllowInsecure)),
		}
	}
	return nil
}

func (p *EnvVarsProvider) tracingEnvVars(plugin *plugins.Plugin) []string {
	if !p.cfg.Tracing.IsEnabled() {
		return nil
	}

	vars := []string{
		p.envVar("GF_INSTANCE_OTLP_ADDRESS", p.cfg.Tracing.OpenTelemetry.Address),
		p.envVar("GF_INSTANCE_OTLP_PROPAGATION", p.cfg.Tracing.OpenTelemetry.Propagation),
		p.envVar("GF_INSTANCE_OTLP_SAMPLER_TYPE", p.cfg.Tracing.OpenTelemetry.Sampler),
		fmt.Sprintf("GF_INSTANCE_OTLP_SAMPLER_PARAM=%.6f", p.cfg.Tracing.OpenTelemetry.SamplerParam),
		p.envVar("GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL", p.cfg.Tracing.OpenTelemetry.SamplerRemoteURL),
	}
	if plugin.Info.Version != "" {
		vars = append(vars, fmt.Sprintf("GF_PLUGIN_VERSION=%s", plugin.Info.Version))
	}
	return vars
}

func (p *EnvVarsProvider) pluginSettingsEnvVars(pluginID string) []string {
	const customConfigPrefix = "GF_PLUGIN"

	pluginSettings := p.cfg.PluginSettings[pluginID]

	env := make([]string, 0, len(pluginSettings))
	for k, v := range pluginSettings {
		if k == "path" || strings.ToLower(k) == "id" {
			continue
		}

		key := fmt.Sprintf("%s_%s", customConfigPrefix, strings.ToUpper(k))
		if value := os.Getenv(key); value != "" {
			v = value
		}

		env = append(env, fmt.Sprintf("%s=%s", key, v))
	}

	return env
}

func (p *EnvVarsProvider) envVar(key, value string) string {
	if strings.Contains(value, "\x00") {
		p.logger.Error("Variable with key '%s' contains NUL", key)
	}
	return fmt.Sprintf("%s=%s", key, value)
}

func (p *EnvVarsProvider) getAzureSettings() *azsettings.AzureSettings {
	azureSettings := p.cfg.Azure
	if azureSettings == nil {
		azureSettings = &azsettings.AzureSettings{}
	}
	azureAdSettings, err := p.ssoSettings.GetForProvider(context.Background(), social.AzureADProviderName)
	if err != nil {
		p.logger.Error("Failed to get SSO settings", "error", err)
	}
	return mergeAzureSettings(azureSettings, azureAdSettings)
}
