import { createEksCluster } from "./kubernetes/eks";
import { createClusterIngressPolicies } from "./kubernetes/ingress_policies";
import {
  createVpc,
  createPrivateDatabaseSubnetGroup,
  getSubnetsByUse,
  createPeeringConnection,
  acceptPeeringConnection,
} from "./networking/vpc";
import { createStudioApiSecret } from "./app/studo-api-secret";
import { createExternalSecretIAMUser } from "./security/external-secret-iam";
import { createEcrRepository } from "./storage/ecr";
import { createElasticCache } from "./cache/redis";
import {
  EndpointConfig,
  createAutoUpdateNLB,
} from "./private-link-nlb/auto-update-nlb";
import * as pulumi from "@pulumi/pulumi";
import { createDatadogSecret } from "./app/dd-external-secret";
import { createGithubSshKeySecret } from "./app/github-ssh-key-secret";
import * as aws from "@pulumi/aws";
import { createProdEksArgocdIamRole } from "./app/prod-argo-iam-role";
import { createStagingArgoIamRole } from "./app/staging-argo-iam-role";
import { createSubnetRouter } from "./networking/tailscale";
import { createElasticSearch } from "./elastic-search/escloud";

const config = new pulumi.Config();
const org = config.require("org");
const stackName = pulumi.getStack();
const vpc = createVpc();
const privateDatabaseSubnetGroup = createPrivateDatabaseSubnetGroup(vpc);

const stackRef = new pulumi.StackReference(`${org}/databases/${stackName}`);
const sunoMainDbEndpoint = stackRef.getOutput(
  "sunoMainDbEndpoint"
) as pulumi.Output<string>;
const sunoMainAllReplicaEndpoints = stackRef.getOutput(
  "sunoMainAllReplicaEndpoints"
) as unknown as pulumi.Output<string>[];
const sunoMainAnalyticsReplicaEndpoint = stackRef.getOutput(
  "sunoMainAnalyticsReplicaEndpoint"
) as unknown as pulumi.Output<string>;

const sunoMainProxyEndpoint = stackRef.getOutput(
  "sunoMainreadWriteProxyEndpoint"
) as pulumi.Output<string>;
const sunoMainReadOnlyProxyEndpoint = stackRef.getOutput(
  "sunoMainreadOnlyProxyEndpoint"
) as pulumi.Output<string>;
const sunoChopinProxyEndpoint = stackRef.getOutput(
  "sunoChopinreadWriteProxyEndpoint"
) as pulumi.Output<string>;

const sunoChopinAllReplicaEndpoints = stackRef.getOutput(
  "sunoChopinAllReplicaEndpoints"
) as unknown as pulumi.Output<string>[];
const sunoMainDbSecretArn = stackRef.getOutput(
  "sunoMainDbSecretArn"
) as pulumi.Output<string>;
const sunoMainDbClusterIdentifier = stackRef.getOutput(
  "sunoMainDbClusterIdentifier"
) as pulumi.Output<string>;
const sunoChopinDbClusterIdentifier = stackRef.getOutput(
  "sunoChopinDbClusterIdentifier"
) as pulumi.Output<string>;
const sunoChopinDbSecretArn = stackRef.getOutput(
  "sunoChopinDbSecretArn"
) as pulumi.Output<string>;

getSubnetsByUse("database").then((subnets) => {
  const { cluster, user } = createElasticCache(vpc, subnets);

  pulumi
    .all([
      sunoMainDbEndpoint,
      sunoMainAnalyticsReplicaEndpoint,
      sunoMainProxyEndpoint,
      sunoMainReadOnlyProxyEndpoint,
      sunoChopinProxyEndpoint,
      cluster.cacheNodes,
    ])
    .apply(
      ([
        sunoMainDbEndpoint,
        sunoMainAnalyticsReplicaEndpoint,
        sunoMainProxyEndpoint,
        sunoMainReadOnlyProxyEndpoint,
        sunoChopinProxyEndpoint,
        cacheNodes,
      ]) => {
        const endpoints: Array<EndpointConfig> = [
          {
            name: "main-db",
            nlbExposedPort: 5432,
            targetPort: 5432,
            url: sunoMainDbEndpoint,
          },
          {
            name: "main-db-proxy-reads",
            nlbExposedPort: 5434,
            targetPort: 5432,
            url: sunoMainReadOnlyProxyEndpoint,
          },
          {
            name: "main-db-analytics",
            nlbExposedPort: 5435,
            targetPort: 5432,
            url: sunoMainAnalyticsReplicaEndpoint,
          },
          {
            name: "main-db-proxy",
            nlbExposedPort: 5436,
            targetPort: 5432,
            url: sunoMainProxyEndpoint,
          },
          {
            name: "chopin-db",
            nlbExposedPort: 5433,
            targetPort: 5432,
            url: sunoChopinProxyEndpoint,
          },
          {
            name: "elasticache-nlb",
            nlbExposedPort: 6379,
            targetPort: 6379,
            url: cacheNodes[0].address,
          },
        ];

        return createAutoUpdateNLB(vpc, subnets, endpoints);
      }
    );
});

export const vpcId = vpc.vpcId;
export const ownerId = vpc.vpc.ownerId;
export const privateDatabaseSubnetGroupName = privateDatabaseSubnetGroup.name;

// Initialize outputs to null, this is required as you can not export outputs conditionally
export let eksClusterName = pulumi.output<string | undefined>(undefined);
export let eksClusterConn = pulumi.output<string | undefined>(undefined);
export let studioApiSecretName = pulumi.output<string | undefined>(undefined);
export let iam = pulumi.output<string | undefined>(undefined);
export let ecrRepositoryURN = pulumi.output<string | undefined>(undefined);
export let stagingPeerId = pulumi.output<string | undefined>(undefined);
export let prodArgoRoleArn = pulumi.output<string | undefined>(undefined);
export let stagingArgoRoleArn = pulumi.output<string | undefined>(undefined);

export let tailscaleSubnetRouterIp = pulumi.output<string | undefined>(
  undefined
);

export let elasticSearchDeployment = pulumi.output<string | undefined>(
  undefined
);

const eksCluster = createEksCluster(vpc);
const githubSecret = createGithubSshKeySecret(eksCluster.provider);
const ingressPolicies = createClusterIngressPolicies(eksCluster);
const studioApiSecret = createStudioApiSecret();
const datadogSecret = createDatadogSecret({
  sunoChopinAllReplicaEndpoints: sunoChopinAllReplicaEndpoints,
  sunoChopinDbSecretArn: sunoChopinDbSecretArn,
  sunoChopinDbClusterIdentifier: sunoChopinDbClusterIdentifier,
  sunoMainAllReplicaEndpoints: sunoMainAllReplicaEndpoints,
  sunoMainDbSecretArn: sunoMainDbSecretArn,
  sunoMainDbClusterIdentifier: sunoMainDbClusterIdentifier,
  cluster: eksCluster,
});
const externalSecretIam = createExternalSecretIAMUser(eksCluster.provider);
const ecrRepository = createEcrRepository(eksCluster.provider);

// Set output values from the created resources
eksClusterName = pulumi.output(eksCluster.eksCluster.name);
eksClusterConn = pulumi.output(
  eksCluster.kubeconfig.apply((x) => x.clusters[0].cluster)
);
studioApiSecretName = pulumi.output(studioApiSecret.name);
iam = pulumi.output(externalSecretIam.name);
ecrRepositoryURN = pulumi.output(ecrRepository.urn);

pulumi.all([vpc.vpcId, vpc.publicSubnetIds]).apply(([vpcId, publicSubnets]) => {
  tailscaleSubnetRouterIp = createSubnetRouter(vpcId, publicSubnets[0]);
});

elasticSearchDeployment = createElasticSearch("song-clip-indexing");

if (stackName === "prod") {
  const stagingStack = new pulumi.StackReference(`${org}/k8s-cluster/staging`);
  const peerId = stagingStack.getOutput("vpcId");
  const ownerId = stagingStack.getOutput("ownerId");
  const clusterName = stagingStack.getOutput("eksClusterName");
  const kubeconfig = stagingStack.getOutput("eksClusterConn");
  const argoRoleArn = stagingStack.getOutput("stagingArgoRoleArn");

  pulumi
    .all([peerId, ownerId, vpc.vpcId])
    .apply(([stagingVpcId, stagingOwnerId, prodVpcId]) => {
      stagingPeerId = pulumi.output(
        createPeeringConnection(
          "staging-vpc",
          prodVpcId,
          stagingVpcId,
          stagingOwnerId
        )
      );
    });

  prodArgoRoleArn = pulumi.output(
    createProdEksArgocdIamRole(eksCluster).role.arn
  );

  const secret = new aws.secretsmanager.Secret("staging-eks-kubeconfig", {
    name: "staging-eks-kubeconfig",
    description: "eks cluster",
  });

  new aws.secretsmanager.SecretVersion("staging-eks-kubeconfig-version", {
    secretId: secret.id,
    secretString: pulumi
      .all([kubeconfig, clusterName, argoRoleArn])
      .apply(([kubeconfig, clusterName, roleArn]) => {
        return JSON.stringify({ ...kubeconfig, clusterName, roleArn });
      }),
  });
} else if (stackName === "staging") {
  const prodStack = new pulumi.StackReference(`${org}/k8s-cluster/prod`);
  const stagingPeerId = prodStack.getOutput("stagingPeerId");
  const prodArgoRoleArn = prodStack.getOutput(
    "prodArgoRoleArn"
  ) as pulumi.Output<string>;

  stagingPeerId.apply((peerId) => {
    if (peerId) {
      acceptPeeringConnection("prod-vpc", peerId);
    }
  });
  stagingArgoRoleArn = pulumi.output(
    createStagingArgoIamRole(prodArgoRoleArn).role.arn
  );
}
