package log_agent

import (
	"fmt"
)

const (
	// ${TenantID}/${ProjectID}/${ApplicationID}/${Category}/${Component}/
	tidbClusterLogsKeyPrefix = "0/${TENANT_ID}/${PROJECT_ID}/{{ `{{ o11y.cluster_id }}` }}/logs/{{ `{{ o11y.component }}` }}/"
	// cluster id for infra log is hardcoded with "k8s-infra", when create shoot o11y cluster.
	infraLogKeyPrefix      = "1/${TENANT_ID}/${PROJECT_ID}/k8s-infra/logs/{{ `{{ o11y.component }}` }}/"
	journaldLogsKeyPrefix  = "1/${TENANT_ID}/${PROJECT_ID}/k8s-infra/logs/journald/{{ `{{ host }}` }}/"
	unmatchedLogsKeyPrefix = "1/${TENANT_ID}/${PROJECT_ID}/k8s-infra/logs/_unmatched/"
)

func BuildAwsDefaultVectorChartValuesForShoot(region, tenantID, projectID, k8sIdentifier, bucket, assumeRoleArn, imageRepo, imageTag string) []byte {
	values := env(tenantID, projectID, k8sIdentifier) + sourceAndTransform() + awsSink(region, bucket, assumeRoleArn) + otherFixture(imageRepo, imageTag)
	return []byte(values)
}

func BuildGcpDefaultVectorChartValuesForShoot(tenantID, projectID, k8sIdentifier, bucket, imageRepo, imageTag string) []byte {
	values := env(tenantID, projectID, k8sIdentifier) + sourceAndTransform() + gcpSink(bucket) + otherFixture(imageRepo, imageTag)
	return []byte(values)
}

func env(tenantID, projectID, k8sIdentifier string) string {
	return fmt.Sprintf(`# env -- Set environment variables in Vector containers
env:
  - name: TENANT_ID
    value: "%s"
  - name: PROJECT_ID
    value: "%s"
  - name: K8S_CLUSTER_NAME
    value: "%s"

`, tenantID, projectID, k8sIdentifier)
}

func sourceAndTransform() string {
	return `# customConfig -- Override Vector's default configs, if used **all** options need to be specified
## This section supports using helm templates to populate dynamic values
## Ref: https://vector.dev/docs/reference/configuration/
customConfig:
  data_dir: /vector-data-dir
  api:
    enabled: true
    address: 0.0.0.0:8686
    playground: false

  sources:
    all_logs:
      type: kubernetes_logs
      delay_deletion_ms: 180000
      # Refer to https://github.com/vectordotdev/vector/issues/7840#issuecomment-921533804
      glob_minimum_cooldown_ms: 5000
    journald:
      type: journald

  transforms:
    ensure_component:
      type: remap
      inputs:
        - all_logs
      drop_on_error: true
      reroute_dropped: true
      source: |-
        if !is_null(.kubernetes.pod_labels."app.kubernetes.io/component") {
          .o11y.component = .kubernetes.pod_labels."app.kubernetes.io/component"
        } else if !is_null(.kubernetes.container_name) {
          .o11y.component = .kubernetes.container_name
        } else {
          .o11y.component = "unknown"
        }
        if !is_null(.kubernetes.container_name) && .kubernetes.container_name == "slowlog" {
          .o11y.component = "slowlog"
        }

    route_logs:
      type: route
      inputs:
        - ensure_component
      route:
        tidb_cluster_general_logs: |- 
          includes(["tidb","tikv","pd","ticdc","tiflash","tiflash-learner","db-tidb-extra-access","backup","restore","tidb-lightning","dm-master","dm-worker","extra-tidb"], .o11y.component)
        tidb_cluster_slow_logs: |- 
          .o11y.component == "slowlog"
        infra_logs: |- 
          !includes(["tidb","tikv","pd","ticdc","tiflash","tiflash-learner","db-tidb-extra-access","backup","restore","tidb-lightning","dm-master","dm-worker","extra-tidb","slowlog"], .o11y.component)

    multiline_slow_logs:
      type: reduce
      inputs:
        - route_logs.tidb_cluster_slow_logs
      starts_when:
        type: vrl
        source: |-
          res, err = starts_with(.message, "# Time: ")
          if err != null {
            false
          } else {
            res
          }
      merge_strategies:
        message: concat_newline

    ensure_cluster_id:
      type: remap
      inputs:
        - multiline_slow_logs
        - route_logs.tidb_cluster_general_logs
      drop_on_error: true
      reroute_dropped: true
      source: |-
        if !is_null(.kubernetes.pod_labels."tags.tidbcloud.com/cluster") {
          .o11y.cluster_id = .kubernetes.pod_labels."tags.tidbcloud.com/cluster"
        } else {
          parsed, err = parse_grok(.kubernetes.pod_namespace, "tidb%{GREEDYDATA:cluster_id}")
          if err == null {
            .o11y.cluster_id = parsed.cluster_id
          } else {
            .o11y.cluster_id = "unknown"
          }
        }

    enrich_tidb_cluster_log_message:
      type: remap
      inputs:
        - ensure_cluster_id
      drop_on_error: true
      reroute_dropped: true
      source: |-
        t = to_string!(.timestamp)
        node = .kubernetes.pod_node_name
        pod_namespace = .kubernetes.pod_namespace
        pod_name = .kubernetes.pod_name
        container_name = .kubernetes.container_name
        prefix = join!([t, node, pod_namespace, pod_name, container_name], ";")
        new_message, err = prefix + " " + .message
        if err == null {
          .message = new_message
        }

    enrich_infra_log_message:
      type: remap
      inputs:
        - route_logs.infra_logs
      drop_on_error: true
      reroute_dropped: true
      source: |-
        t = to_string!(.timestamp)
        node = .kubernetes.pod_node_name
        pod_namespace = .kubernetes.pod_namespace
        pod_name = .kubernetes.pod_name
        container_name = .kubernetes.container_name
        prefix = join!([t, node, pod_namespace, pod_name, container_name], ";")
        new_message, err = prefix + " " + .message
        if err == null {
          .message = new_message
        }

`
}

func awsSink(region, bucket, assumeRoleArn string) string {
	return fmt.Sprintf(`  sinks:
    s3_infra_logs:
      type: aws_s3
      inputs:
        - enrich_infra_log_message
      region: %s
      bucket: %s
      key_prefix: |-
        %s
      compression: gzip
      batch:
        max_bytes: 104857600 # 100MB
        timeout_secs: 180 # 3 minutes
      buffer:
        type: disk
        max_size: 1073741824 # 1GB
      encoding:
        codec: text
      auth:
        # hostNetwork must be enabled
        assume_role: %s
      tags:
        k8s: ${K8S_CLUSTER_NAME}

    s3_tidb_cluster_logs:
      type: aws_s3
      inputs:
        - enrich_tidb_cluster_log_message
      region: %s
      bucket: %s
      key_prefix: |-
        %s
      compression: gzip
      batch:
        max_bytes: 104857600 # 100MB
        timeout_secs: 180 # 3 minutes
      buffer:
        type: disk
        max_size: 1073741824 # 1GB
      encoding:
        codec: text
      auth:
        # hostNetwork must be enabled
        assume_role: %s
      tags:
        k8s: ${K8S_CLUSTER_NAME}

    s3_unmatched_kubernetes_logs:
      type: aws_s3
      inputs:
        - ensure_component.dropped
        - route_logs._unmatched
        - ensure_cluster_id.dropped
        - enrich_tidb_cluster_log_message.dropped
        - enrich_infra_log_message.dropped
      region: %s
      bucket: %s
      key_prefix: |-
        %s
      compression: gzip
      batch:
        max_bytes: 104857600 # 100MB
        timeout_secs: 180 # 3 minutes
      buffer:
        type: disk
        max_size: 1073741824 # 1GB
      encoding:
        codec: json
      auth:
        # hostNetwork must be enabled
        assume_role: %s
      tags:
        k8s: ${K8S_CLUSTER_NAME}

    s3_journald_logs:
      type: aws_s3
      inputs:
        - journald
      region: %s
      bucket: %s
      key_prefix: |-
        %s
      compression: gzip
      batch:
        max_bytes: 104857600 # 100MB
        timeout_secs: 180 # 3 minutes
      buffer:
        type: disk
        max_size: 1073741824 # 1GB
      encoding:
        codec: text
      auth:
        # hostNetwork must be enabled
        assume_role: %s
      tags:
        k8s: ${K8S_CLUSTER_NAME}

`,
		region,
		bucket,
		infraLogKeyPrefix,
		assumeRoleArn,

		region,
		bucket,
		tidbClusterLogsKeyPrefix,
		assumeRoleArn,

		region,
		bucket,
		unmatchedLogsKeyPrefix,
		assumeRoleArn,

		region,
		bucket,
		journaldLogsKeyPrefix,
		assumeRoleArn,
	)
}

func gcpSink(bucket string) string {
	return fmt.Sprintf(`  sinks:
    gcs_infra_logs:
      type: gcp_cloud_storage
      inputs:
        - enrich_infra_log_message
      bucket: %s
      key_prefix: |-
        %s
      compression: gzip
      batch:
        max_bytes: 104857600 # 100MB
        timeout_secs: 180 # 3 minutes
      buffer:
        type: disk
        max_size: 1073741824 # 1GB
      encoding:
        codec: text
      metadata:
        x-goog-meta-k8s: ${K8S_CLUSTER_NAME}

    gcs_tidb_cluster_logs:
      type: gcp_cloud_storage
      inputs:
        - enrich_tidb_cluster_log_message
      bucket: %s
      key_prefix: |-
        %s
      compression: gzip
      batch:
        max_bytes: 104857600 # 100MB
        timeout_secs: 180 # 3 minutes
      buffer:
        type: disk
        max_size: 1073741824 # 1GB
      encoding:
        codec: text
      metadata:
        x-goog-meta-k8s: ${K8S_CLUSTER_NAME}

    gcs_unmatched_kubernetes_logs:
      type: gcp_cloud_storage
      inputs:
        - ensure_component.dropped
        - route_logs._unmatched
        - ensure_cluster_id.dropped
        - enrich_tidb_cluster_log_message.dropped
        - enrich_infra_log_message.dropped
      bucket: %s
      key_prefix: |-
        %s
      compression: gzip
      batch:
        max_bytes: 104857600 # 100MB
        timeout_secs: 180 # 3 minutes
      buffer:
        type: disk
        max_size: 1073741824 # 1GB
      encoding:
        codec: json
      metadata:
        x-goog-meta-k8s: ${K8S_CLUSTER_NAME}

    gcs_journald_logs:
      type: gcp_cloud_storage
      inputs:
        - journald
      bucket: %s
      key_prefix: |-
        %s
      compression: gzip
      batch:
        max_bytes: 104857600 # 100MB
        timeout_secs: 180 # 3 minutes
      buffer:
        type: disk
        max_size: 1073741824 # 1GB
      encoding:
        codec: text
      metadata:
        x-goog-meta-k8s: ${K8S_CLUSTER_NAME}

`,
		bucket,
		infraLogKeyPrefix,

		bucket,
		tidbClusterLogsKeyPrefix,

		bucket,
		unmatchedLogsKeyPrefix,

		bucket,
		journaldLogsKeyPrefix,
	)
}

func otherFixture(imageRepo, imageTag string) string {
	return fmt.Sprintf(`# resources -- Set Vector resource requests and limits.
resources:
  requests:
    cpu: "10m"
    memory: "100Mi"
  limits:
    cpu: "500m"
    memory: "1000Mi"

role: "Agent"

## Configuration for Vector's data persistence
## Persistence always used for Agent role
persistence:
  hostPath:
    # persistence.hostPath.path -- Override path used for hostPath persistence
    ## Valid for Agent role
    path: "/var/lib/vector/01"

# Mount machine id to fix journalctl. Refer to https://github.com/vectordotdev/vector/issues/5225
# extraVolumes -- Additional Volumes to use with Vector Pods
extraVolumes:
  - name: machine-id
    hostPath:
      path: /etc/machine-id
      type: File

# extraVolumeMounts -- Additional Volume to mount into Vector Containers
extraVolumeMounts:
  - name: machine-id
    mountPath: /etc/machine-id
    readOnly: true

# rollWorkload -- Add a checksum of the generated ConfigMap to workload annotations
rollWorkload: true

## Define the Vector image to use
image:
  # image.repository -- Override default registry + name for Vector
  repository: %s
  # image.pullPolicy -- Vector image pullPolicy
  pullPolicy: IfNotPresent
  tag: %s

# dnsPolicy -- Specify DNS policy for Vector Pods
## Ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
dnsPolicy: ClusterFirstWithHostNet

## Configure a HorizontalPodAutoscaler for Vector
## Valid for Aggregator and Stateless-Aggregator roles
autoscaling:
  # autoscaling.enabled -- Enabled autoscaling for the Aggregator and Stateless-Aggregator
  enabled: false
  # autoscaling.minReplicas -- Minimum replicas for Vector's HPA

podDisruptionBudget:
  # podDisruptionBudget.enabled -- Enable a PodDisruptionBudget for Vector
  enabled: false

rbac:
  # rbac.create -- If true, create and use RBAC resources. Only valid for the Agent role
  create: true

psp:
  # psp.create -- If true, create a PodSecurityPolicy resource. PodSecurityPolicy is deprecated as of Kubernetes v1.21, and will be removed in v1.25
  ## Intended for use with the Agent role
  create: false

serviceAccount:
  # serviceAccount.create -- If true, create ServiceAccount
  create: true
  # serviceAccount.automountToken -- Automount API credentials for the Vector ServiceAccount
  automountToken: true

# tolerations -- Allow Vector to schedule on tainted nodes
tolerations:
  - operator: Exists

## Configuration for Vector's Service
service:
  # service.enabled -- If true, create and use a Service resource
  enabled: false

## Configuration for Vector's Ingress
ingress:
  # ingress.enabled -- If true, create and use an Ingress resource
  enabled: false

# livenessProbe -- Override default liveness probe settings, if customConfig is used requires customConfig.api.enabled true
## Requires Vector's API to be enabled
livenessProbe:
  httpGet:
    path: /health
    port: api

# readinessProbe -- Override default readiness probe settings, if customConfig is used requires customConfig.api.enabled true
## Requires Vector's API to be enabled
readinessProbe:
  httpGet:
    path: /health
    port: api

## Configure a PodMonitor for Vector
## Requires the PodMonitor CRD to be installed
podMonitor:
  # podMonitor.enabled -- If true, create a PodMonitor for Vector
  enabled: false

## Optional built-in HAProxy load balancer
haproxy:
  # haproxy.enabled -- If true, create a HAProxy load balancer
  enabled: false
`, imageRepo, imageTag)
}
