// Package global provides CLI global flags.
package global

import (
	"context"
	"fmt"
	"slices"

	"github.com/gruntwork-io/terragrunt/internal/cli/commands/help"
	"github.com/gruntwork-io/terragrunt/internal/cli/commands/version"
	"github.com/gruntwork-io/terragrunt/internal/cli/flags"
	"github.com/gruntwork-io/terragrunt/internal/clihelper"
	"github.com/gruntwork-io/terragrunt/internal/experiment"
	"github.com/gruntwork-io/terragrunt/internal/strict"
	"github.com/gruntwork-io/terragrunt/internal/strict/controls"
	"github.com/gruntwork-io/terragrunt/pkg/log"
	"github.com/gruntwork-io/terragrunt/pkg/log/format"
	"github.com/gruntwork-io/terragrunt/pkg/options"
)

const (
	// Logs related flags.

	LogLevelFlagName        = "log-level"
	LogDisableFlagName      = "log-disable"
	ShowLogAbsPathsFlagName = "log-show-abs-paths"
	LogFormatFlagName       = "log-format"
	LogCustomFormatFlagName = "log-custom-format"
	NoColorFlagName         = "no-color"

	NonInteractiveFlagName = "non-interactive"
	WorkingDirFlagName     = "working-dir"

	// Strict Mode related flags.

	StrictModeFlagName    = "strict-mode"
	StrictControlFlagName = "strict-control"

	// Experiment Mode related flags/envs.

	ExperimentModeFlagName = "experiment-mode"
	ExperimentFlagName     = "experiment"

	// Tips related flags.

	NoTipsFlagName = "no-tips"
	NoTipFlagName  = "no-tip"

	// App flags.

	HelpFlagName    = "help"
	VersionFlagName = "version"

	// Telemetry flags.

	TelemetryTraceExporterFlagName                  = "telemetry-trace-exporter"
	TelemetryTraceExporterInsecureEndpointFlagName  = "telemetry-trace-exporter-insecure-endpoint"
	TelemetryTraceExporterHTTPEndpointFlagName      = "telemetry-trace-exporter-http-endpoint"
	TraceparentFlagName                             = "traceparent"
	TelemetryMetricExporterFlagName                 = "telemetry-metric-exporter"
	TelemetryMetricExporterInsecureEndpointFlagName = "telemetry-metric-exporter-insecure-endpoint"
	TelemetryLogsExporterFlagName                   = "telemetry-logs-exporter"
	TelemetryLogsExporterInsecureEndpointFlagName   = "telemetry-logs-exporter-insecure-endpoint"

	// Profiling flags.

	ProfileCPUFlagName       = "profile-cpu"
	ProfileMemFlagName       = "profile-mem"
	ProfileGoroutineFlagName = "profile-goroutine"
	ProfileDirFlagName       = "profile-dir"

	// Renamed flags.

	DeprecatedLogLevelFlagName        = "log-level"
	DeprecatedLogDisableFlagName      = "log-disable"
	DeprecatedShowLogAbsPathsFlagName = "log-show-abs-paths"
	DeprecatedLogFormatFlagName       = "log-format"
	DeprecatedLogCustomFormatFlagName = "log-custom-format"
	DeprecatedNoColorFlagName         = "no-color"
	DeprecatedNonInteractiveFlagName  = "non-interactive"
	DeprecatedTFInputFlagName         = "tf-input"
	DeprecatedWorkingDirFlagName      = "working-dir"
	DeprecatedStrictModeFlagName      = "strict-mode"
	DeprecatedStrictControlFlagName   = "strict-control"
	DeprecatedExperimentModeFlagName  = "experiment-mode"
	DeprecatedExperimentFlagName      = "experiment"

	// Deprecated flags.

	DeprecatedDisableLogFormattingFlagName = "disable-log-formatting"
	DeprecatedJSONLogFlagName              = "json-log"
	DeprecatedTfLogJSONFlagName            = "tf-logs-to-json"
)

// NewFlags creates and returns global flags common for all commands.
func NewFlags(l log.Logger, opts *options.TerragruntOptions, prefix flags.Prefix) clihelper.Flags {
	tgPrefix := prefix.Prepend(flags.TgPrefix)
	terragruntPrefix := prefix.Prepend(flags.TerragruntPrefix)
	flags := clihelper.Flags{
		NewLogLevelFlag(l, opts, prefix),

		flags.NewFlag(
			&clihelper.GenericFlag[string]{
				Name:        WorkingDirFlagName,
				EnvVars:     tgPrefix.EnvVars(WorkingDirFlagName),
				Destination: &opts.WorkingDir,
				Usage:       "The path to the directory of Terragrunt configurations. Default is current directory.",
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedWorkingDirFlagName),
				opts.StrictControls,
			),
		),

		flags.NewFlag(
			&clihelper.BoolFlag{
				Name:    LogDisableFlagName,
				EnvVars: tgPrefix.EnvVars(LogDisableFlagName),
				Usage:   "Disable logging.",
				Setter: func(val bool) error {
					l.Formatter().SetDisabledOutput(val)

					if val {
						opts.ForwardTFStdout = true
					}

					return nil
				},
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedLogDisableFlagName),
				opts.StrictControls,
			),
		),

		flags.NewFlag(
			&clihelper.BoolFlag{
				Name:        ShowLogAbsPathsFlagName,
				EnvVars:     tgPrefix.EnvVars(ShowLogAbsPathsFlagName),
				Destination: &opts.LogShowAbsPaths,
				Usage:       "Show absolute paths in logs.",
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedShowLogAbsPathsFlagName),
				opts.StrictControls,
			),
		),

		flags.NewFlag(
			&clihelper.BoolFlag{
				Name:    NoColorFlagName,
				EnvVars: tgPrefix.EnvVars(NoColorFlagName),
				Usage:   "Disable color output.",
				Setter: func(val bool) error {
					l.Formatter().SetDisabledColors(val)
					return nil
				},
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedNoColorFlagName),
				opts.StrictControls,
			),
		),

		flags.NewFlag(
			&clihelper.GenericFlag[string]{
				Name:    LogFormatFlagName,
				EnvVars: tgPrefix.EnvVars(LogFormatFlagName),
				Usage:   "Set the log format.",
				Setter:  l.Formatter().SetFormat,
				Action: func(_ context.Context, _ *clihelper.Context, val string) error {
					switch val {
					case format.BareFormatName:
						opts.ForwardTFStdout = true
					case format.JSONFormatName:
						opts.JSONLogFormat = true
					}

					return nil
				},
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedLogFormatFlagName),
				opts.StrictControls,
			),
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedDisableLogFormattingFlagName),
				opts.StrictControls,
				controls.LegacyLogs,
			),
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedJSONLogFlagName),
				opts.StrictControls,
				controls.LegacyLogs,
			),
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedTfLogJSONFlagName),
				opts.StrictControls,
				controls.LegacyLogs,
			),
		),

		flags.NewFlag(
			&clihelper.GenericFlag[string]{
				Name:    LogCustomFormatFlagName,
				EnvVars: tgPrefix.EnvVars(LogCustomFormatFlagName),
				Usage:   "Set the custom log formatting.",
				Setter:  l.Formatter().SetCustomFormat,
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedLogCustomFormatFlagName),
				opts.StrictControls,
			),
		),

		flags.NewFlag(
			&clihelper.BoolFlag{
				Name:        NonInteractiveFlagName,
				EnvVars:     tgPrefix.EnvVars(NonInteractiveFlagName),
				Destination: &opts.NonInteractive,
				Usage:       `Assume "yes" for all prompts.`,
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedNonInteractiveFlagName),
				opts.StrictControls,
			),
			flags.WithDeprecatedFlag(&clihelper.BoolFlag{
				Negative: true,
				EnvVars:  flags.Prefix{}.EnvVars(DeprecatedTFInputFlagName),
			}, nil, opts.StrictControls),
		),

		// Experiment Mode flags.

		flags.NewFlag(
			&clihelper.BoolFlag{
				Name:    ExperimentModeFlagName,
				EnvVars: tgPrefix.EnvVars(ExperimentModeFlagName),
				Usage:   "Enables experiment mode for Terragrunt. For more information, see https://docs.terragrunt.com/reference/experiment-mode .",
				Setter: func(_ bool) error {
					opts.Experiments.ExperimentMode()

					return nil
				},
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedExperimentModeFlagName),
				opts.StrictControls,
			),
		),

		flags.NewFlag(
			&clihelper.SliceFlag[string]{
				Name:    ExperimentFlagName,
				EnvVars: tgPrefix.EnvVars(ExperimentFlagName),
				Usage:   "Enables specific experiments. For a list of available experiments, see https://docs.terragrunt.com/reference/experiment-mode .",
				Setter:  opts.Experiments.EnableExperiment,
				Action: func(_ context.Context, _ *clihelper.Context, _ []string) error {
					opts.Experiments.NotifyCompletedExperiments(l)

					return nil
				},
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedExperimentFlagName),
				opts.StrictControls,
			),
		),

		// Tips Mode flags.

		flags.NewFlag(&clihelper.BoolFlag{
			Name:    NoTipsFlagName,
			EnvVars: tgPrefix.EnvVars(NoTipsFlagName),
			Usage:   "Disable all tips from being displayed.",
			Setter: func(v bool) error {
				if v {
					opts.Tips.DisableAll()
				}

				return nil
			},
		}),

		flags.NewFlag(&clihelper.SliceFlag[string]{
			Name:    NoTipFlagName,
			EnvVars: tgPrefix.EnvVars(NoTipFlagName),
			Usage:   "Disable specific tips from being displayed.",
			Setter:  opts.Tips.DisableTip,
		}),

		// Strict Mode flags.

		flags.NewFlag(
			&clihelper.BoolFlag{
				Name:    StrictModeFlagName,
				EnvVars: tgPrefix.EnvVars(StrictModeFlagName),
				Usage:   "Enables strict mode for Terragrunt. For more information, run 'terragrunt info strict'.",
				Setter: func(_ bool) error {
					opts.StrictControls.FilterByStatus(strict.ActiveStatus).Enable()

					return nil
				},
				Action: func(_ context.Context, _ *clihelper.Context, _ bool) error {
					opts.StrictControls.LogEnabled(l)

					return nil
				},
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedStrictModeFlagName),
				opts.StrictControls,
			),
		),

		flags.NewFlag(
			&clihelper.SliceFlag[string]{
				Name:    StrictControlFlagName,
				EnvVars: tgPrefix.EnvVars(StrictControlFlagName),
				Usage:   "Enables specific strict controls. For a list of available controls, run 'terragrunt info strict'.",
				Setter: func(val string) error {
					return opts.StrictControls.EnableControl(val)
				},
				Action: func(_ context.Context, _ *clihelper.Context, vals []string) error {
					opts.StrictControls.LogEnabled(l)
					opts.StrictControls.LogCompletedControls(l, vals)

					return nil
				},
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars(DeprecatedStrictControlFlagName),
				opts.StrictControls,
			),
		),
	}

	flags = flags.Add(NewTelemetryFlags(opts, nil)...)
	flags = flags.Add(NewProfileFlags(opts, nil)...)
	flags = flags.Sort()
	flags = flags.Add(NewHelpVersionFlags(l, opts)...)

	return flags
}

// NewTelemetryFlags creates telemetry related flags.
func NewTelemetryFlags(opts *options.TerragruntOptions, prefix flags.Prefix) clihelper.Flags {
	tgPrefix := prefix.Prepend(flags.TgPrefix)
	terragruntPrefix := prefix.Prepend(flags.TerragruntPrefix)

	return clihelper.Flags{
		flags.NewFlag(
			&clihelper.GenericFlag[string]{
				EnvVars:     tgPrefix.EnvVars(TelemetryTraceExporterFlagName),
				Destination: &opts.Telemetry.TraceExporter,
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars("telemetry-trace-exporter"),
				opts.StrictControls,
			),
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars("telemerty-trace-exporter"),
				opts.StrictControls,
			),
		),

		flags.NewFlag(
			&clihelper.BoolFlag{
				EnvVars:     tgPrefix.EnvVars(TelemetryTraceExporterInsecureEndpointFlagName),
				Destination: &opts.Telemetry.TraceExporterInsecureEndpoint,
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars("telemetry-trace-exporter-insecure-endpoint"),
				opts.StrictControls,
			),
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars("telemerty-trace-exporter-insecure-endpoint"),
				opts.StrictControls,
			),
		),

		flags.NewFlag(
			&clihelper.GenericFlag[string]{
				EnvVars:     tgPrefix.EnvVars(TelemetryTraceExporterHTTPEndpointFlagName),
				Destination: &opts.Telemetry.TraceExporterHTTPEndpoint,
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars("telemetry-trace-exporter-http-endpoint"),
				opts.StrictControls,
			),
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars("telemerty-trace-exporter-http-endpoint"),
				opts.StrictControls,
			),
		),

		flags.NewFlag(&clihelper.GenericFlag[string]{
			EnvVars:     flags.Prefix{}.EnvVars(TraceparentFlagName),
			Destination: &opts.Telemetry.TraceParent,
		}),
		flags.NewFlag(
			&clihelper.GenericFlag[string]{
				EnvVars:     tgPrefix.EnvVars(TelemetryMetricExporterFlagName),
				Destination: &opts.Telemetry.MetricExporter,
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars("telemetry-metric-exporter"),
				opts.StrictControls,
			),
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars("telemerty-metric-exporter"),
				opts.StrictControls,
			),
		),

		flags.NewFlag(
			&clihelper.BoolFlag{
				EnvVars:     tgPrefix.EnvVars(TelemetryMetricExporterInsecureEndpointFlagName),
				Destination: &opts.Telemetry.MetricExporterInsecureEndpoint,
			},
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars("telemetry-metric-exporter-insecure-endpoint"),
				opts.StrictControls,
			),
			flags.WithDeprecatedEnvVars(
				terragruntPrefix.EnvVars("telemerty-metric-exporter-insecure-endpoint"),
				opts.StrictControls,
			),
		),

		flags.NewFlag(&clihelper.GenericFlag[string]{
			EnvVars:     tgPrefix.EnvVars(TelemetryLogsExporterFlagName),
			Destination: &opts.Telemetry.LogsExporter,
		}),

		flags.NewFlag(&clihelper.BoolFlag{
			EnvVars:     tgPrefix.EnvVars(TelemetryLogsExporterInsecureEndpointFlagName),
			Destination: &opts.Telemetry.LogsExporterInsecureEndpoint,
		}),
	}
}

// NewProfileFlags creates the profiling flags backing the opts.Profile* fields.
func NewProfileFlags(opts *options.TerragruntOptions, prefix flags.Prefix) clihelper.Flags {
	tgPrefix := prefix.Prepend(flags.TgPrefix)

	return clihelper.Flags{
		flags.NewFlag(&clihelper.GenericFlag[string]{
			Name:        ProfileCPUFlagName,
			EnvVars:     tgPrefix.EnvVars(ProfileCPUFlagName),
			Destination: &opts.ProfileCPU,
			Usage:       profileFlagUsage(opts, "Write a CPU profile to the given path."),
		}),

		flags.NewFlag(&clihelper.GenericFlag[string]{
			Name:        ProfileMemFlagName,
			EnvVars:     tgPrefix.EnvVars(ProfileMemFlagName),
			Destination: &opts.ProfileMem,
			Usage:       profileFlagUsage(opts, "Write a memory (heap) profile to the given path."),
		}),

		flags.NewFlag(&clihelper.GenericFlag[string]{
			Name:        ProfileGoroutineFlagName,
			EnvVars:     tgPrefix.EnvVars(ProfileGoroutineFlagName),
			Destination: &opts.ProfileGoroutine,
			Usage:       profileFlagUsage(opts, "Write a goroutine profile to the given path."),
		}),

		flags.NewFlag(&clihelper.GenericFlag[string]{
			Name:        ProfileDirFlagName,
			EnvVars:     tgPrefix.EnvVars(ProfileDirFlagName),
			Destination: &opts.ProfileDir,
			Usage:       profileFlagUsage(opts, "Directory to write profile files (cpu, mem, goroutine)."),
		}),
	}
}

// profileFlagUsage appends the experiment requirement to the usage text while the profiling experiment is ongoing.
func profileFlagUsage(opts *options.TerragruntOptions, usage string) string {
	exp := opts.Experiments.Find(experiment.Profiling)
	if exp == nil || exp.Status != experiment.StatusOngoing {
		return usage
	}

	return usage + " Requires the 'profiling' experiment."
}

func NewLogLevelFlag(
	l log.Logger,
	opts *options.TerragruntOptions,
	prefix flags.Prefix,
) *flags.Flag {
	tgPrefix := prefix.Prepend(flags.TgPrefix)
	terragruntPrefix := prefix.Prepend(flags.TerragruntPrefix)

	return flags.NewFlag(&clihelper.GenericFlag[string]{
		Name:        LogLevelFlagName,
		EnvVars:     tgPrefix.EnvVars(LogLevelFlagName),
		DefaultText: l.Level().String(),
		Setter:      l.SetLevel,
		Usage: fmt.Sprintf(
			"Sets the logging level for Terragrunt. Supported levels: %s.",
			log.AllLevels,
		),
		Action: func(_ context.Context, _ *clihelper.Context, val string) error {
			// Before the release of v0.67.0, these levels actually disabled logs, since we do not use these levels for logging.
			// For backward compatibility we simulate the same behavior.
			removedLevels := []string{
				"panic",
				"fatal",
			}

			if slices.Contains(removedLevels, val) {
				opts.ForwardTFStdout = true

				l.Formatter().SetDisabledOutput(true)
			}

			return nil
		},
	}, flags.WithDeprecatedEnvVars(terragruntPrefix.EnvVars(DeprecatedLogLevelFlagName), opts.StrictControls))
}

func NewHelpVersionFlags(l log.Logger, opts *options.TerragruntOptions) clihelper.Flags {
	return clihelper.Flags{
		&clihelper.BoolFlag{
			Name:    HelpFlagName,  // --help, -help
			Aliases: []string{"h"}, //  -h
			Usage:   "Show help.",
			Action: func(ctx context.Context, cliCtx *clihelper.Context, _ bool) error {
				return help.Action(ctx, cliCtx, l, opts)
			},
		},
		&clihelper.BoolFlag{
			Name:    VersionFlagName, // --version, -version
			Aliases: []string{"v"},   //  -v
			Usage:   "Show terragrunt version.",
			Action: func(ctx context.Context, cliCtx *clihelper.Context, _ bool) (err error) {
				return version.Action(ctx, cliCtx)
			},
		},
	}
}
