import * as k8s from '@pulumi/kubernetes';
import * as kx from '@pulumi/kubernetesx';
import { Output } from '@pulumi/pulumi';
import { memoryParser } from './k8s';
import { getLocalComposeConfig } from './local-config';
import { normalizeEnv, PodBuilder } from './pod-builder';
import { createService } from './service-deployment';

const REDIS_PORT = 6379;
const METRICS_PORT = 9121;
const REDIS_EXPORTER_IMAGE = 'oliver006/redis_exporter:v1.70.0-alpine';

export class Redis {
  constructor(
    protected options: {
      env?: kx.types.Container['env'];
      password: Output<string>;
    },
  ) {}

  deploy(input: { limits: { memory: string; cpu: string }; requests: { cpu: string } }) {
    const redisService = getLocalComposeConfig().service('redis');
    const name = 'redis-store';
    const limits: k8s.types.input.core.v1.ResourceRequirements['limits'] = {
      memory: input.limits.memory,
      cpu: input.limits.cpu,
    };
    const requests: k8s.types.input.core.v1.ResourceRequirements['requests'] = {
      cpu: input.requests.cpu,
    };

    const env: k8s.types.input.core.v1.EnvVar[] = normalizeEnv(this.options.env ?? {}).concat([
      {
        name: 'REDIS_PASSWORD',
        value: this.options.password,
      },
      {
        name: 'POD_NAME',
        valueFrom: {
          fieldRef: {
            fieldPath: 'metadata.name',
          },
        },
      },
    ] satisfies k8s.types.input.core.v1.EnvVar[]);

    const cm = new kx.ConfigMap('redis-scripts', {
      data: {
        'readiness.sh': this.options.password.apply(
          p => `#!/bin/bash
        response=$(timeout -s SIGTERM 3 $1 redis-cli -h localhost -a ${p} -p ${REDIS_PORT} ping)
        if [ "$response" != "PONG" ]; then
          echo "$response"
          exit 1
        fi
                `,
        ),
        'liveness.sh': this.options.password.apply(
          p => `#!/bin/bash
        response=$(timeout -s SIGTERM 3 $1 redis-cli -h localhost -a ${p} -p ${REDIS_PORT} ping)
        if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then
          echo "$response"
          exit 1
        fi
                `,
        ),
      },
    });

    const volumeMounts = [cm.mount('/scripts')];

    // Redis Exporter container environment variables
    const exporterEnv: k8s.types.input.core.v1.EnvVar[] = [
      {
        name: 'REDIS_ADDR',
        value: `redis://localhost:${REDIS_PORT}`,
      },
      {
        name: 'REDIS_PASSWORD',
        value: this.options.password,
      },
      {
        name: 'REDIS_EXPORTER_LOG_FORMAT',
        value: 'json',
      },
      {
        name: 'REDIS_EXPORTER_STREAMS_EXCLUDE_CONSUMER_METRICS',
        value: 'true',
      },
    ];

    const memoryInBytes = memoryParser(input.limits.memory) * 0.9; // Redis recommends 80%
    const memoryInMegabytes = Math.floor(memoryInBytes / 1024 / 1024);

    const pb = new PodBuilder({
      restartPolicy: 'Always',
      containers: [
        {
          name,
          image: redisService.image,
          env,
          volumeMounts,
          ports: [{ containerPort: REDIS_PORT, protocol: 'TCP' }],
          resources: {
            limits,
            requests,
          },
          livenessProbe: {
            initialDelaySeconds: 3,
            periodSeconds: 10,
            failureThreshold: 10,
            timeoutSeconds: 3,
            exec: {
              command: ['/bin/sh', '/scripts/liveness.sh'],
            },
          },
          // Note: this is needed, otherwise local config is not loaded at all
          command: ['/opt/bitnami/scripts/redis/entrypoint.sh'],
          // This is where we can pass actual flags to the bitnami/redis runtime
          args: [
            '/opt/bitnami/scripts/redis/run.sh',
            `--maxmemory ${memoryInMegabytes}mb`,
            // This disables snapshotting to save cpu and reduce latency spikes
            '--save ""',
          ],
          readinessProbe: {
            initialDelaySeconds: 5,
            periodSeconds: 8,
            failureThreshold: 5,
            timeoutSeconds: 3,
            exec: {
              command: ['/bin/sh', '/scripts/readiness.sh'],
            },
          },
        },
        {
          name: 'redis-exporter',
          image: REDIS_EXPORTER_IMAGE,
          env: exporterEnv,
          ports: [{ containerPort: METRICS_PORT, protocol: 'TCP', name: 'metrics' }],
          resources: {
            limits: {
              cpu: '200m',
              memory: '200Mi',
            },
            requests: {
              cpu: '100m',
              memory: '100Mi',
            },
          },
        },
      ],
    });

    const deployment = new kx.Deployment(name, {
      spec: pb.asExtendedDeploymentSpec(
        {
          replicas: 1,
          strategy: {
            type: 'RollingUpdate',
            rollingUpdate: {
              maxSurge: 1,
              maxUnavailable: 0,
            },
          },
        },
        {
          annotations: {
            'prometheus.io/scrape': 'true',
            'prometheus.io/port': String(METRICS_PORT),
            'prometheus.io/path': '/metrics',
          },
        },
      ),
    });

    new k8s.policy.v1.PodDisruptionBudget('redis-pdb', {
      spec: {
        minAvailable: 1,
        selector: deployment.spec.selector,
      },
    });

    const service = createService(name, deployment);

    return { deployment, service, redisPort: REDIS_PORT, metricsPort: METRICS_PORT };
  }
}
