{ config, pkgs, lib, ... }:

let
  formatPrivateKey = indentLevel: value:
    let
      indent = lib.strings.fixedWidthString indentLevel " " "";
      indentedLines = lib.strings.concatMapStrings
        (line: "${indent}${line}\n")
        (lib.strings.splitString "\n" value);
    in
    "|\n${indentedLines}";
in
{
  # Node Nix Config
  options = {
    serverAddr = lib.mkOption {
      type = lib.types.str;
      description = "The server to join";
      default = "";
    };
    democraticConfig = lib.mkOption {
      type = lib.types.submodule {
        options = {
          apiKeyFile = lib.mkOption {
            type = lib.types.path;
            description = "Path to file containing the TrueNAS API key";
          };
          sshKeyFile = lib.mkOption {
            type = lib.types.path;
            description = "Path to file containing the TrueNAS User SSH private key";
          };
        };
      };
    };
    networkConfig = lib.mkOption {
      type = lib.types.submodule {
        options = {
          interface = lib.mkOption {
            type = lib.types.str;
            description = "Network interface name";
            example = "enp0s3";
          };
          address = lib.mkOption {
            type = lib.types.str;
            description = "Static IP address";
            example = "10.0.20.200";
          };
          defaultGateway = lib.mkOption {
            type = lib.types.str;
            description = "Default gateway IP";
            example = "10.0.20.254";
          };
          nameservers = lib.mkOption {
            type = lib.types.listOf lib.types.str;
            description = "List of DNS servers";
            example = [ "10.0.20.254" "8.8.8.8" ];
            default = [ "8.8.8.8" "8.8.4.4" ];
          };
        };
      };
      description = "Network configuration";
    };
  };

  config = {
    # ----------------------------------------
    # ---------- Base Configuration ----------
    # ----------------------------------------
    # Democratic Requirements
    boot.initrd = {
      availableKernelModules = [ "xen_blkfront" "xen_netfront" ];
      kernelModules = [ "xen_netfront" "xen_blkfront" ];
      supportedFilesystems = [ "ext4" "xenfs" ];
    };

    boot.kernelModules = [
      # Xen VM Requirements
      "xen_netfront"
      "xen_blkfront"
      "xenfs"

      # iSCSI & Multipath
      "iscsi_tcp"
      "dm_multipath"
      "dm_round_robin"
    ];

    # Network Configuration
    networking = {
      hostName = config.hostName;
      networkmanager.enable = false;

      # Interface Configuration
      inherit (config.networkConfig) defaultGateway nameservers;
      interfaces."${config.networkConfig.interface}" = {
        mtu = 9000;
        ipv4.addresses = [{
          address = config.networkConfig.address;
          prefixLength = 24;
        }];
      };

      firewall = {
        enable = true;

        allowedTCPPorts = [
          # RKE2 Ports - https://docs.rke2.io/install/requirements#networking
          6443 # Kubernetes API
          9345 # RKE2 supervisor API
          2379 # etcd Client Port
          2380 # etcd Peer Port
          2381 # etcd Metrics Port
          10250 # kubelet metrics
          9099 # Canal CNI health checks
        ];

        allowedUDPPorts = [
          # RKE2 Ports - https://docs.rke2.io/install/requirements#networking
          8472 # Canal CNI with VXLAN
          # 51820 # Canal CNI with WireGuard IPv4 (if using encryption)
          # 51821 # Canal CNI with WireGuard IPv6 (if using encryption)
        ];
      };
    };

    # System Packages
    environment.systemPackages = with pkgs; [
      htop
      k9s
      kubectl
      kubernetes-helm
      openiscsi
      tmux
      vim
    ];

    # ----------------------------------------
    # ---------- RKE2 Configuration ----------
    # ----------------------------------------

    # RKE2 Join Token
    environment.etc."rancher/rke2/node-token" = lib.mkIf (config.serverAddr != "") {
      source = ../_scratch/rke2-token;
      mode = "0600";
      user = "root";
      group = "root";
    };

    # Enable RKE2
    services.rke2 = {
      enable = true;
      role = "server";

      disable = [
        # Disable - Utilizing Traefik
        "rke2-ingress-nginx"

        # Disable
        # "rke2-snapshot-controller"
        # "rke2-snapshot-controller-crd"
        # "rke2-snapshot-validation-webhook"
      ];
    } // lib.optionalAttrs (config.serverAddr != "") {
      serverAddr = config.serverAddr;
      tokenFile = "/etc/rancher/rke2/node-token";
    };

    # Enable Xe Guest Utilities
    services.xe-guest-utilities.enable = true;

    # Enable OpeniSCSI
    services.openiscsi = {
      enable = true;
      name = "iqn.2025-02.${config.hostName}:initiator";
    };

    # Enable Multipath
    services.multipath = {
      enable = true;
      defaults = ''
        defaults {
            user_friendly_names yes
            find_multipaths yes
        }
      '';
      pathGroups = [ ];
    };

    time.timeZone = "UTC";

    # Add Symlinks Expected by Democratic
    system.activationScripts.add-symlinks = ''
      mkdir -p /usr/bin
      ln -sf ${pkgs.openiscsi}/bin/iscsiadm /usr/bin/iscsiadm
      ln -sf ${pkgs.openiscsi}/bin/iscsid /usr/bin/iscsid
    '';

    # Bootstrap Kubernetes Manifests
    system.activationScripts.k8s-manifests = {
      deps = [ ];
      text = ''
        mkdir -p /var/lib/rancher/rke2/server/manifests

        # Base Configs
        cp ${pkgs.substituteAll {
          src = ../k8s/democratic.yaml;
          apiKey = lib.strings.removeSuffix "\n" (builtins.readFile config.democraticConfig.apiKeyFile);
          privateKey = formatPrivateKey 12 (lib.strings.removeSuffix "\n" (builtins.readFile config.democraticConfig.sshKeyFile));
        }} /var/lib/rancher/rke2/server/manifests/democratic-base.yaml

        cp ${../k8s/kasten.yaml} /var/lib/rancher/rke2/server/manifests/kasten-base.yaml
      '';
    };
  };
}