package common

import (
	"context"
	"fmt"
	"strings"

	"gopkg.in/yaml.v3"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/helm"
)

type ServiceProxy struct {
	*task.BaseTask
	config *serviceProxyConfig
}

type serviceProxyConfig struct {
	*config.InfraComponentConfig
	kongAuditConfig
	persistentVolumeClaimLabels map[string]string
}

func (c *serviceProxyConfig) ToHelmValues() map[string]interface{} {
	values := c.InfraComponentConfig.ToHelmValues()

	cfg, ok := values["config"].(map[string]interface{})
	if !ok {
		cfg = map[string]interface{}{}
		values["config"] = cfg
	}
	cfg["auditLog"] = c.EnableLog

	// Use the shared function to set audit sidecar config
	// This ensures path consistency with `pkg/service/dataplane/kong_audit_log.go`
	SetAuditSidecarConfig(values, &AuditSidecarInput{
		BucketURL:   c.BucketURL,
		Region:      c.Region,
		RoleARN:     c.AWS.RoleARN,
		ExternalID:  c.AWS.ExternalID,
		SASToken:    c.Azure.SASToken,
		MaxSize:     c.MaxSize,
		MaxLifetime: c.MaxLifetime,
	})

	// Service proxy stores audit logs on an ephemeral PVC created from the Pod spec.
	// Unlike StatefulSet volumeClaimTemplates, this template can be updated through the
	// Deployment rollout path, so we attach tag labels in Helm values and let newly
	// created PVCs inherit them directly.
	if len(c.persistentVolumeClaimLabels) > 0 {
		values["persistentVolumeClaimLabels"] = c.persistentVolumeClaimLabels
	}

	return values
}

func (c *serviceProxyConfig) isAuditSidecarEnabled() bool {
	values := c.InfraComponentConfig.ToHelmValues()
	as, ok := values["auditSidecar"]
	if !ok {
		return false
	}
	asMap, ok := as.(map[string]interface{})
	if !ok {
		return false
	}
	enabled, ok := asMap["enabled"]
	if !ok {
		return false
	}
	enabledBool, ok := enabled.(bool)
	return ok && enabledBool
}

func NewServiceProxy(ctx *task.Context) *ServiceProxy {
	return &ServiceProxy{
		BaseTask: &task.BaseTask{Context: ctx},
		config: &serviceProxyConfig{
			InfraComponentConfig: &ctx.DedicatedTmplConfig.ServiceProxy,
		},
	}
}

func (t *ServiceProxy) Name() string {
	return "ServiceProxy"
}

func (t *ServiceProxy) Create(ctx context.Context) deploy.Result {
	t.config.persistentVolumeClaimLabels = persistentVolumeClaimLabelsForAppBox(t.APPBox, "service-proxy")

	// If it's deployed with the audit sidecar,
	// need to get the bucket info from the kong audit configmap.
	if t.config.isAuditSidecarEnabled() {
		t.Logger.Info("service-proxy audit sidecar is enabled, fetching kong audit config")

		cli, err := t.Remote.GetClient(ctx)
		if err != nil {
			return deploy.Error(err)
		}

		cfg, err := getKongAuditConfig(ctx, cli)
		if err != nil {
			return deploy.Error(fmt.Errorf("failed to get kong audit config: %w", err))
		}
		t.Logger.Info("fetched kong audit config", "config", cfg)

		if cfg.EnableLog {
			t.config.EnableLog = true
			t.config.BucketURL = strings.TrimSpace(cfg.BucketURL)
			t.config.Region = strings.TrimSpace(cfg.Region)
			t.config.AWS = cfg.AWS
			t.config.Azure = cfg.Azure
			t.config.MaxSize = cfg.MaxSize
			t.config.MaxLifetime = cfg.MaxLifetime
		} else {
			t.config.EnableLog = false
		}
	}

	return deploy.ErrorOrDone(helm.Apply(ctx, t.Remote, t.config))
}

func (t *ServiceProxy) PostCreate(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(controllers.EnsureChartDeploymentReady(ctx, t.Remote, types.NamespacedName{
		Namespace: t.config.GetNamespace(),
		Name:      "service-proxy",
	}))
}

type kongAuditConfig struct {
	EnableLog   bool        `yaml:"enableLog"`
	BucketURL   string      `yaml:"bucketURL"`
	Region      string      `yaml:"region"`
	MaxSize     int         `yaml:"maxSize"`
	MaxLifetime int         `yaml:"maxLifetime"`
	AWS         awsConfig   `yaml:"aws"`
	Azure       azureConfig `yaml:"azure"`
}

type awsConfig struct {
	RoleARN    string `yaml:"roleARN"`
	ExternalID string `yaml:"externalID"`
}

type azureConfig struct {
	SASToken string `yaml:"sasToken"`
}

func getKongAuditConfig(ctx context.Context, cli client.Client) (*kongAuditConfig, error) {
	var cm corev1.ConfigMap
	if err := cli.Get(ctx, types.NamespacedName{
		Namespace: task.DedicatedAdminNamespace,
		Name:      kongAuditConfigMapNameInDataplane,
	}, &cm); err != nil {
		return nil, fmt.Errorf("failed to get kong audit configmap in dataplane: %w", err)
	}

	cfg, ok := cm.Data[kongAuditConfigKey]
	if !ok {
		return nil, fmt.Errorf("kong audit configmap %s does not contain key %s", kongAuditConfigMapNameInDataplane, kongAuditConfigKey)
	}

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