import config from "../config.ts";
import axios from "axios";
import { VMManager, type VM } from "./base.ts";
import fs from "node:fs";
import { redis } from "../utils/redis.ts";

const HETZNER_TOKEN = config.HETZNER_TOKEN;
const sshKeys = config.HETZNER_SSH_KEYS.split(",").map(Number);

export class Hetzner extends VMManager {
  size = "cpx11"; // cpx11, cx22 (not available in US yet)
  largeSize = "cpx31"; // cpx21, cpx31, ccx13, ccx23, cx32
  minRetries = 5;
  reuseVMs = true;
  id = "Hetzner";
  gateway = config.HETZNER_GATEWAY;
  imageId = config.HETZNER_IMAGE;

  private getRandomDatacenter() {
    // US
    let datacenters = ["ash"];
    if (this.region === "USW") {
      datacenters = ["hil"];
    } else if (this.region === "EU") {
      datacenters = ["nbg1", "fsn1", "hel1"];
    }
    return datacenters[Math.floor(Math.random() * datacenters.length)];
  }

  startVM = async (name: string) => {
    const data = {
      name: name,
      server_type: this.isLarge ? this.largeSize : this.size,
      start_after_create: true,
      image: Number(this.imageId),
      ssh_keys: sshKeys,
      public_net: {
        enable_ipv4: true,
        enable_ipv6: false,
      },
      // networks: [
      //   this.networks[Math.floor(Math.random() * this.networks.length)],
      // ],
      // user_data: `replace with vbrowser.sh startup script if we want to boot vbrowser on instance creation (won't trigger on rebuild/restart)`
      labels: {
        [this.getTag()]: "1",
      },
      location: this.getRandomDatacenter(),
    };
    const response = await axios({
      method: "POST",
      url: `https://api.hetzner.cloud/v1/servers`,
      headers: {
        Authorization: "Bearer " + HETZNER_TOKEN,
        "Content-Type": "application/json",
        "User-Agent": "test",
      },
      data,
    });
    const id = response.data.server.id;
    return id;
  };

  terminateVM = async (id: string) => {
    await axios({
      method: "DELETE",
      url: `https://api.hetzner.cloud/v1/servers/${id}`,
      headers: {
        Authorization: "Bearer " + HETZNER_TOKEN,
        "User-Agent": "test",
      },
    });
  };

  rebootVM = async (id: string) => {
    // Reboot the VM
    await axios({
      method: "POST",
      url: `https://api.hetzner.cloud/v1/servers/${id}/actions/reboot`,
      headers: {
        Authorization: "Bearer " + HETZNER_TOKEN,
        "User-Agent": "test",
      },
    });
    return;
  };

  reimageVM = async (id: string) => {
    // Rebuild the VM
    await axios({
      method: "POST",
      url: `https://api.hetzner.cloud/v1/servers/${id}/actions/rebuild`,
      headers: {
        Authorization: "Bearer " + HETZNER_TOKEN,
        "User-Agent": "test",
      },
      data: {
        image: Number(this.imageId),
      },
    });
    return;
  };

  getVM = async (id: string) => {
    const response: any = await axios({
      method: "GET",
      url: `https://api.hetzner.cloud/v1/servers/${id}`,
      headers: {
        Authorization: "Bearer " + HETZNER_TOKEN,
        "User-Agent": "test",
      },
    });
    console.log(
      "[GETVM] %s: %s rate limit remaining",
      id,
      response?.headers["ratelimit-remaining"],
    );
    await redis?.set(
      "hetznerApiRemaining",
      response?.headers["ratelimit-remaining"],
    );
    const server = this.mapServerObject(response.data.server);
    return server;
  };

  listVMs = async (filter: string) => {
    const limit = this.getLimitSize();
    const pageCount = Math.ceil((limit || 1) / 50);
    const pages = Array.from(Array(pageCount).keys()).map((i) => i + 1);
    const responses: any[] = await Promise.all(
      pages.map((page) =>
        axios({
          method: "GET",
          url: `https://api.hetzner.cloud/v1/servers`,
          headers: {
            Authorization: "Bearer " + HETZNER_TOKEN,
            "User-Agent": "test",
          },
          params: {
            sort: "id:asc",
            page,
            per_page: 50,
            label_selector: filter,
          },
        }),
      ),
    );
    const responsesMapped = responses.map((response) =>
      response.data.servers.map(this.mapServerObject),
    );
    return responsesMapped.flat();
  };

  powerOn = async (id: string) => {
    // Poweron the server (usually not needed)
    try {
      await axios({
        method: "POST",
        url: `https://api.hetzner.cloud/v1/servers/${id}/actions/poweron`,
        headers: {
          Authorization: "Bearer " + HETZNER_TOKEN,
          "Content-Type": "application/json",
          "User-Agent": "test",
        },
      });
    } catch (e) {
      console.log("%s failed to poweron", id);
    }
  };

  attachToNetwork = async (id: string) => {
    // // Attach server to network (usually not needed)
    // try {
    //   const response: any = await axios({
    //     method: 'GET',
    //     url: `https://api.hetzner.cloud/v1/servers/${id}`,
    //     headers: {
    //       Authorization: 'Bearer ' + HETZNER_TOKEN,
    //     },
    //   });
    //   if (response.data.server.private_net?.[0] == null) {
    //     await axios({
    //       method: 'POST',
    //       url: `https://api.hetzner.cloud/v1/servers/${id}/actions/attach_to_network`,
    //       headers: {
    //         Authorization: 'Bearer ' + HETZNER_TOKEN,
    //         'Content-Type': 'application/json',
    //       },
    //       data: {
    //         network:
    //           this.networks[Math.floor(Math.random() * this.networks.length)],
    //       },
    //     });
    //   }
    // } catch (e: any) {
    //   console.log('%s failed to attach to network', id);
    //   console.log(e.response?.data);
    // }
  };

  updateSnapshot = async () => {
    const response = await axios({
      method: "POST",
      url: `https://api.hetzner.cloud/v1/servers`,
      headers: {
        Authorization: "Bearer " + HETZNER_TOKEN,
        "Content-Type": "application/json",
        "User-Agent": "test",
      },
      data: {
        name: "vBrowserSnapshot",
        server_type: "cpx11",
        start_after_create: true,
        image: "docker-ce", // 15512617 for Ubuntu 20.04
        ssh_keys: sshKeys,
        user_data: fs
          .readFileSync(import.meta.dirname + "/../../dev/vbrowser.sh")
          .toString()
          .replace("{VBROWSER_ADMIN_KEY}", config.VBROWSER_ADMIN_KEY),
        location: this.getRandomDatacenter(),
      },
    });
    const id = response.data.server.id;
    await new Promise((resolve) => setTimeout(resolve, 4 * 60 * 1000));
    // Validate snapshot server was created successfully
    // const response3 = await axios(
    //   'http://' + response.data.server.public_net?.ipv4?.ip + ':5000'
    // );
    const response2 = await axios({
      method: "POST",
      url: `https://api.hetzner.cloud/v1/servers/${id}/actions/create_image`,
      headers: {
        Authorization: "Bearer " + HETZNER_TOKEN,
        "Content-Type": "application/json",
      },
    });
    const imageId = response2.data.image.id;
    await this.terminateVM(id);
    return imageId;
  };

  mapServerObject = (server: any): VM => {
    const public_ip = server.public_net?.ipv4?.ip;
    // const private_ip = server.private_net?.[0]?.ip;
    const ip = public_ip;
    // We can use either the public or private IP for communicating between gateway and VM
    // Only signaling traffic goes through here since the video is transmitted over WebRTC
    // The private IP requires the server and gateway to be on the same network and there is a limit to the number of servers allowed
    return {
      id: server.id?.toString(),
      // The gateway handles SSL termination and proxies to the private IP
      host: ip ? `${this.gateway}/?ip=${ip}` : "",
      provider: this.id,
      large: this.isLarge,
      region: this.region,
    };
  };
}
