// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// Package splunk provides functionalities for integrating the splunk logging driver
// with shim-loggers-for-containerd.
package splunk

import (
	"context"
	"fmt"

	"github.com/containerd/containerd/runtime/v2/logging"
	dockerlogger "github.com/docker/docker/daemon/logger"
	dockersplunk "github.com/docker/docker/daemon/logger/splunk"

	"github.com/aws/shim-loggers-for-containerd/debug"
	"github.com/aws/shim-loggers-for-containerd/logger"
)

// splunk driver argument keys.
const (
	// DriverName is the name of the splunk log driver.
	DriverName = "splunk"

	// Required.

	TokenKey = "splunk-token"
	URLKey   = "splunk-url"

	// Optional.

	SourceKey             = "splunk-source"
	SourcetypeKey         = "splunk-sourcetype"
	IndexKey              = "splunk-index"
	CapathKey             = "splunk-capath"
	CanameKey             = "splunk-caname"
	InsecureskipverifyKey = "splunk-insecureskipverify"
	FormatKey             = "splunk-format"
	VerifyConnectionKey   = "splunk-verify-connection"
	GzipKey               = "splunk-gzip"
	GzipLevelKey          = "splunk-gzip-level"
	SplunkTagKey          = "splunk-tag"
	LabelsKey             = "labels"
	EnvKey                = "env"
	EnvRegexKey           = "env-regex"

	// Convert input parameter "splunk-tag" to the splunk parameter "tag".
	// This is to distinguish between the "tag" parameter from the fluentd input.
	tagKey = "tag"
)

// Args represents splunk log driver arguments.
type Args struct {
	Token              string
	URL                string
	Source             string
	Sourcetype         string
	Index              string
	Capath             string
	Caname             string
	Insecureskipverify string
	Format             string
	VerifyConnection   string
	Gzip               string
	GzipLevel          string
	Tag                string
	// TagSpecified represents whether a splunk tag was specified. It is used to differentiate between the default value
	// of the splunk tag when the flag is initialized and the client specifying the default value of the tag,
	// as they may be the same.
	TagSpecified bool
	Labels       string
	Env          string
	EnvRegex     string
}

// LoggerArgs stores global logger args and splunk specific args.
type LoggerArgs struct {
	globalArgs    *logger.GlobalArgs
	dockerConfigs *logger.DockerConfigs
	args          *Args
}

// InitLogger initialize the input arguments.
func InitLogger(globalArgs *logger.GlobalArgs, dockerConfigs *logger.DockerConfigs, splunkArgs *Args) *LoggerArgs {
	return &LoggerArgs{
		globalArgs:    globalArgs,
		dockerConfigs: dockerConfigs,
		args:          splunkArgs,
	}
}

// RunLogDriver initiates the splunk driver.
func (la *LoggerArgs) RunLogDriver(ctx context.Context, config *logging.Config, ready func() error) error {
	defer debug.DeferFuncForRunLogDriver()

	loggerConfig, err := getSplunkConfig(la.args)
	if err != nil {
		debug.ErrLogger = fmt.Errorf("unable to validate log options: %w", err)
		return debug.ErrLogger
	}
	info := logger.NewInfo(
		la.globalArgs.ContainerID,
		la.globalArgs.ContainerName,
		logger.WithConfig(loggerConfig),
	)
	info = logger.UpdateDockerConfigs(info, la.dockerConfigs)

	stream, err := dockersplunk.New(*info)
	if err != nil {
		debug.ErrLogger = fmt.Errorf("unable to create stream: %w", err)
		return debug.ErrLogger
	}

	l, err := logger.NewLogger(
		logger.WithStdout(config.Stdout),
		logger.WithStderr(config.Stderr),
		logger.WithInfo(info),
		logger.WithStream(stream),
	)
	if err != nil {
		debug.ErrLogger = fmt.Errorf("unable to create splunk log driver: %w", err)
		return debug.ErrLogger
	}

	if la.globalArgs.Mode == logger.NonBlockingMode {
		debug.SendEventsToLog(logger.DaemonName, "Starting non-blocking mode driver", debug.INFO, 0)
		l = logger.NewBufferedLogger(l, logger.DefaultBufSizeInBytes, la.globalArgs.MaxBufferSize, la.globalArgs.ContainerID)
	}

	// Start splunk log driver.
	debug.SendEventsToLog(logger.DaemonName, "Starting splunk driver", debug.INFO, 0)
	err = l.Start(ctx, la.globalArgs.CleanupTime, ready)
	if err != nil {
		debug.ErrLogger = fmt.Errorf("failed to run splunk driver: %w", err)
		// Do not return error if log driver has issue sending logs to destination, because if error
		// returned here, containerd will identify this error and kill shim process, which will kill
		// the container process accordingly.
		// Note: the container will continue to run if shim logger exits after.
		// Reference: https://github.com/containerd/containerd/blob/release/1.3/runtime/v2/logging/logging.go
		return nil
	}
	debug.SendEventsToLog(logger.DaemonName, "Logging finished", debug.INFO, 1)

	return nil
}

// getSplunkConfig sets values for splunk config.
func getSplunkConfig(arg *Args) (map[string]string, error) {
	config := make(map[string]string)
	// Required arguments
	config[TokenKey] = arg.Token
	config[URLKey] = arg.URL
	// Optional arguments
	if arg.Source != "" {
		config[SourceKey] = arg.Source
	}
	if arg.Sourcetype != "" {
		config[SourcetypeKey] = arg.Sourcetype
	}
	if arg.Index != "" {
		config[IndexKey] = arg.Index
	}
	if arg.Capath != "" {
		config[CapathKey] = arg.Capath
	}
	if arg.Caname != "" {
		config[CanameKey] = arg.Caname
	}
	if arg.Insecureskipverify != "" {
		config[InsecureskipverifyKey] = arg.Insecureskipverify
	}
	if arg.Format != "" {
		config[FormatKey] = arg.Format
	}
	if arg.VerifyConnection != "" {
		config[VerifyConnectionKey] = arg.VerifyConnection
	}
	if arg.Gzip != "" {
		config[GzipKey] = arg.Gzip
	}
	if arg.GzipLevel != "" {
		config[GzipLevelKey] = arg.GzipLevel
	}
	if arg.TagSpecified {
		config[tagKey] = arg.Tag
	}
	if arg.Labels != "" {
		config[LabelsKey] = arg.Labels
	}
	if arg.Env != "" {
		config[EnvKey] = arg.Env
	}
	if arg.EnvRegex != "" {
		config[EnvRegexKey] = arg.EnvRegex
	}

	err := dockerlogger.ValidateLogOpts(DriverName, config)
	if err != nil {
		return nil, err
	}
	return config, nil
}
