package action

import (
	"context"
	"fmt"
	"strings"

	"github.com/samber/lo"
	"github.com/tidbcloud/cluster-service-ng/config"
	"gopkg.in/yaml.v3"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	// Default values for ClusterAgentAuditConfig.
	// These must match the default tags in config.ClusterAgentAuditConfig.
	defaultClusterAgentAuditStorageClass = "cloud-ssd" // matches config.go:93
	defaultClusterAgentAuditStorageSize  = "1Gi"       // matches config.go:94

	clusterAgentAuditConfigNamespace         = "tidb-admin"
	clusterAgentAuditConfigMapName           = "kong-audit-config"
	clusterAgentAuditConfigKey               = "config.yaml"
	clusterAgentAuditLogDir                  = "/var/log/cluster-agent"
	clusterAgentAuditLogFileName             = "ops-audit.log"
	clusterAgentAuditSidecarMode             = "agent"
	clusterAgentAuditSidecarRotationStrategy = "copytruncate"
	clusterAgentAuditSidecarSubPath          = "cluster-agent"
	clusterAgentAuditSidecarCheckInterval    = "5s"
	clusterAgentAuditSidecarCleanInterval    = "60s"
	clusterAgentAuditSidecarFilesPerPush     = 5
	clusterAgentAuditSidecarDefaultMaxSize   = 10
)

type clusterAgentKongAuditConfig struct {
	EnableLog   bool                 `yaml:"enableLog"`
	BucketURL   string               `yaml:"bucketURL"`
	Region      string               `yaml:"region"`
	MaxSize     int                  `yaml:"maxSize"`
	MaxLifetime int                  `yaml:"maxLifetime"`
	AWS         clusterAgentAuditAWS `yaml:"aws"`
}

type clusterAgentAuditAWS struct {
	RoleARN    string `yaml:"roleARN,omitempty"`
	ExternalID string `yaml:"externalID,omitempty"`
}

type clusterAgentAuditSidecarConfig struct {
	Mode             string                               `yaml:"mode,omitempty"`
	RotationStrategy string                               `yaml:"rotationStrategy,omitempty"`
	SubPath          string                               `yaml:"subPath,omitempty"`
	PushTrigger      *clusterAgentAuditSidecarPushTrigger `yaml:"pushTrigger,omitempty"`
	LogDir           string                               `yaml:"logDir,omitempty"`
	AuditLogFileName string                               `yaml:"auditLogFileName,omitempty"`
	BucketURL        string                               `yaml:"bucketURL"`
	Region           string                               `yaml:"region"`
	AWS              *clusterAgentAuditAWS                `yaml:"aws,omitempty"`
}

type clusterAgentAuditSidecarPushTrigger struct {
	CheckInterval string `yaml:"checkInterval,omitempty"`
	CleanInterval string `yaml:"cleanInterval,omitempty"`
	FilesPerPush  *int   `yaml:"filesPerPush,omitempty"`
	MaxSize       *int   `yaml:"maxSize,omitempty"`
	MaxLifetime   *int   `yaml:"maxLifetime,omitempty"`
}

// populateClusterAgentOptionalTemplateArgs populates optional template arguments
// for cluster agent deployment, including Kong plugins and audit configuration.
func populateClusterAgentOptionalTemplateArgs(templArgs map[string]string, bizConfig *config.BizConfig) {
	if bizConfig == nil {
		return
	}
	populateClusterAgentKongPlugins(templArgs, bizConfig)
	populateClusterAgentAuditConfig(templArgs, bizConfig)
	populateClusterAgentPrivilegedTiDBArgs(templArgs, bizConfig)
}

// populateClusterAgentPrivilegedTiDBArgs maps BizConfig knobs onto the
// cluster_agent.templ flags that tune the privileged TiDB lifecycle.
func populateClusterAgentPrivilegedTiDBArgs(templArgs map[string]string, bizConfig *config.BizConfig) {
	if bizConfig == nil || bizConfig.PrivilegedTiDBDeactivationWait <= 0 {
		return
	}
	templArgs["PrivilegedTiDBDeactivationWait"] = bizConfig.PrivilegedTiDBDeactivationWait.String()
}

// populateClusterAgentKongPlugins populates Kong plugin configuration for cluster agent.
func populateClusterAgentKongPlugins(templArgs map[string]string, bizConfig *config.BizConfig) {
	if bizConfig == nil {
		return
	}
	if bizConfig.KongDataplanePlugins != "" {
		templArgs["KongPlugins"] = bizConfig.KongDataplanePlugins
	}
}

// populateClusterAgentAuditConfig populates audit configuration for cluster agent,
// including storage class and size for the audit log PVC.
func populateClusterAgentAuditConfig(templArgs map[string]string, bizConfig *config.BizConfig) {
	if bizConfig == nil || !bizConfig.ClusterAgentAudit.Enabled {
		return
	}

	templArgs["AuditEnabled"] = "true"

	storageClass := strings.TrimSpace(bizConfig.ClusterAgentAudit.StorageClass)
	if storageClass == "" {
		storageClass = defaultClusterAgentAuditStorageClass
	}
	templArgs["AuditStorageClass"] = storageClass

	storageSize := strings.TrimSpace(bizConfig.ClusterAgentAudit.StorageSize)
	if storageSize == "" {
		storageSize = defaultClusterAgentAuditStorageSize
	}
	templArgs["AuditStorageSize"] = storageSize
}

func populateClusterAgentAuditTemplateArgs(
	templArgs map[string]string,
	imageConfig *config.ImageConfig,
	kongCfg *clusterAgentKongAuditConfig,
) error {
	if imageConfig != nil {
		templArgs["AuditLogImage"] = fmt.Sprintf("%s/%s", imageConfig.Registry, imageConfig.AuditLogImage)
	}

	sidecarConfig, err := genClusterAgentAuditSidecarConfig(kongCfg)
	if err != nil {
		return fmt.Errorf("failed to generate cluster agent audit sidecar config: %w", err)
	}
	templArgs["AuditSidecarConfig"] = sidecarConfig

	if kongCfg != nil && kongCfg.EnableLog {
		templArgs["AuditLogEnabled"] = "true"
	}
	return nil
}

func genClusterAgentAuditSidecarConfig(kongCfg *clusterAgentKongAuditConfig) (string, error) {
	sidecarCfg := &clusterAgentAuditSidecarConfig{
		Mode:             clusterAgentAuditSidecarMode,
		RotationStrategy: clusterAgentAuditSidecarRotationStrategy,
		SubPath:          clusterAgentAuditSidecarSubPath,
		PushTrigger: &clusterAgentAuditSidecarPushTrigger{
			CheckInterval: clusterAgentAuditSidecarCheckInterval,
			CleanInterval: clusterAgentAuditSidecarCleanInterval,
			FilesPerPush:  lo.ToPtr(clusterAgentAuditSidecarFilesPerPush),
			MaxSize:       lo.ToPtr(clusterAgentAuditSidecarDefaultMaxSize),
		},
		LogDir:           clusterAgentAuditLogDir,
		AuditLogFileName: clusterAgentAuditLogFileName,
		BucketURL:        "",
		Region:           "",
	}

	if kongCfg != nil && kongCfg.EnableLog {
		bucketURL := strings.TrimSpace(kongCfg.BucketURL)
		region := strings.TrimSpace(kongCfg.Region)
		if bucketURL != "" && region != "" {
			sidecarCfg.BucketURL = bucketURL
			sidecarCfg.Region = region
			// Only override defaults when the kong config explicitly sets a positive value;
			// zero means the field was absent from the YAML and audit-ng treats 0 as
			// "disable rotation", which would prevent log upload and eventually fill the PVC.
			if kongCfg.MaxSize > 0 {
				sidecarCfg.PushTrigger.MaxSize = lo.ToPtr(kongCfg.MaxSize)
			}
			if kongCfg.MaxLifetime > 0 {
				sidecarCfg.PushTrigger.MaxLifetime = lo.ToPtr(kongCfg.MaxLifetime)
			}

			roleARN := strings.TrimSpace(kongCfg.AWS.RoleARN)
			externalID := strings.TrimSpace(kongCfg.AWS.ExternalID)
			if roleARN != "" || externalID != "" {
				sidecarCfg.AWS = &clusterAgentAuditAWS{
					RoleARN:    roleARN,
					ExternalID: externalID,
				}
			}
		}
	}

	data, err := yaml.Marshal(sidecarCfg)
	if err != nil {
		return "", fmt.Errorf("failed to marshal cluster agent audit sidecar config: %w", err)
	}
	return string(data), nil
}

func getClusterAgentKongAuditConfig(ctx context.Context, cli client.Client) (*clusterAgentKongAuditConfig, error) {
	var cm corev1.ConfigMap
	if err := cli.Get(ctx, types.NamespacedName{
		Namespace: clusterAgentAuditConfigNamespace,
		Name:      clusterAgentAuditConfigMapName,
	}, &cm); err != nil {
		return nil, fmt.Errorf("failed to get cluster agent audit configmap in dataplane: %w", err)
	}

	cfgData, ok := cm.Data[clusterAgentAuditConfigKey]
	if !ok {
		return nil, fmt.Errorf(
			"cluster agent audit configmap %s does not contain key %s",
			clusterAgentAuditConfigMapName,
			clusterAgentAuditConfigKey,
		)
	}

	var cfg clusterAgentKongAuditConfig
	if err := yaml.Unmarshal([]byte(cfgData), &cfg); err != nil {
		return nil, fmt.Errorf("failed to unmarshal cluster agent audit config: %w", err)
	}

	cfg.BucketURL = strings.TrimSpace(cfg.BucketURL)
	cfg.Region = strings.TrimSpace(cfg.Region)
	cfg.AWS.RoleARN = strings.TrimSpace(cfg.AWS.RoleARN)
	cfg.AWS.ExternalID = strings.TrimSpace(cfg.AWS.ExternalID)

	return &cfg, nil
}
