package common

import (
	"context"
	"fmt"
	"path/filepath"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/eks-provider/controllers/cluster/task/common/audit"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
)

// AuditLog is a task that CRUD audit log related resources, like ConfigMaps.
// This task is only for tidb operator v2.
type AuditLog struct {
	*task.BaseTask
}

func NewAuditLog(ctx *task.Context) *AuditLog {
	return &AuditLog{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

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

func (t *AuditLog) Create(ctx context.Context) deploy.Result {
	if !t.Cluster.FeatureEnabled(consts.ClusterFeatureUseTiDBOperatorV2) {
		return deploy.Done()
	}

	auditSpec := t.Cluster.Spec.Cluster.TiDB.Audit
	if auditSpec == nil || !auditSpec.Enable {
		return deploy.Done()
	}

	if err := t.syncConfigMap(ctx, auditSpec); err != nil {
		return deploy.Error(err)
	}

	return deploy.ErrorOrDone(t.syncSecret(ctx))
}

// calGracefulStopWaitSeconds calculates the graceful stop wait seconds for the audit log container.
// When the audit log container receives a SIGTERM signal,
// it will wait for `gracefulStopWaitSeconds` seconds to wait for tidb to stop to ensure the audit file will not be written.
// Different from v1's calculation, the time is calculated by deducing from the tidb pod's TerminationGracePeriodSeconds.
func (t *AuditLog) calGracefulStopWaitSeconds(ctx context.Context) int {
	var podList corev1.PodList
	if err := t.EnvProvider.GetClient().List(ctx, &podList, client.InNamespace(t.Cluster.PrefixedId()), client.MatchingLabels{
		"pingcap.com/cluster":   "db",
		"pingcap.com/component": "tidb",
	}); err != nil {
		t.Logger.Error(err, "failed to list tidb's pods")
	}

	tidbTerminationGracePeriodSeconds := consts.DefaultPodTerminationGracePeriodSeconds
	for _, pod := range podList.Items {
		if pod.Spec.TerminationGracePeriodSeconds != nil {
			tidbTerminationGracePeriodSeconds = int(*pod.Spec.TerminationGracePeriodSeconds)
			break
		}
	}

	return consts.GetAuditLogSidecarGracefulStopWaitSeconds(ctx, t.Cluster, tidbTerminationGracePeriodSeconds)
}

func (t *AuditLog) syncConfigMap(ctx context.Context, auditSpec *cloudv1.Audit) error {
	cfg := audit.Config{
		LogDir:                  audit.MountPath,
		BucketURL:               auditSpec.BucketURL,
		Region:                  auditSpec.Region,
		RoleARN:                 auditSpec.RoleArn,
		ExternalID:              t.GetAWSAuditLogRoleExternalID(),
		SlowLogMaxBackups:       1,
		CleanOldestLogPercent:   0.8,
		GracefulStopWaitSeconds: t.calGracefulStopWaitSeconds(ctx),
		MetricsPort:             audit.MetricsPort,
	}

	if auditSpec.Azure != nil && auditSpec.Azure.SASToken != "" {
		cfg.Azure = &audit.Azure{SASToken: auditSpec.Azure.SASToken}
	}

	data, err := cfg.ToYAML()
	if err != nil {
		return fmt.Errorf("failed to marshal audit config to yaml: %w", err)
	}

	cm := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: t.Cluster.PrefixedId(),
			Name:      audit.ConfigMapName(t.Cluster.Name),
		},
	}
	mp := map[string]string{
		"audit.yaml": string(data),
	}
	_, err = controllerutil.CreateOrUpdate(ctx, t.EnvProvider.GetClient(), cm, func() error {
		cm.Data = mp
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to create or update the configmap for auditlog: %w", err)
	}
	return nil
}

func (t *AuditLog) syncSecret(ctx context.Context) error {
	if t.Config.Provider == cloudv1.ProviderTypeGKE {
		saKey, err := audit.GetServiceAccountKey(ctx, t.Local, t.Cluster.Namespace, t.GetGCPNodePoolServiceAccountKeySecretName())
		if err != nil {
			return fmt.Errorf("failed to get gcp service account key: %w", err)
		}

		secret := &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: t.Cluster.PrefixedId(),
				Name:      audit.GCPSecretName,
			},
		}
		if _, err = controllerutil.CreateOrUpdate(ctx, t.EnvProvider.GetClient(), secret, func() error {
			secret.Data = map[string][]byte{
				filepath.Base(audit.GCPCredentialPath): saKey,
			}
			return nil
		}); err != nil {
			return fmt.Errorf("failed to create or update gcp secret for audit log: %w", err)
		}
	}
	return nil
}

func (t *AuditLog) PostCreate(_ context.Context) deploy.Result {
	return deploy.Done()
}

func (t *AuditLog) Delete(_ context.Context) deploy.Result {
	return deploy.Done()
}

func (t *AuditLog) PostDelete(_ context.Context) deploy.Result {
	return deploy.Done()
}
