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

with lib;
let
  mapOptional = f: val: if (val != null) then (f val) else null;

  host = import ../types/host.nix { inherit lib; };

in {
  options.fudo.hosts = with types;
    mkOption {
      type = attrsOf (submodule host.hostOpts);
      description = "Host configurations for all hosts known to the system.";
      default = { };
    };

  config = let
    hostname = config.instance.hostname;
    host-cfg = config.fudo.hosts.${hostname};
    site-name = host-cfg.site;
    site = config.fudo.sites.${site-name};
    domain-name = host-cfg.domain;
    domain = config.fudo.domains.${domain-name};
    has-build-servers = (length (attrNames site.build-servers)) > 0;
    has-build-keys = (length host-cfg.build-pubkeys) > 0;

  in {
    networking = {
      hostName = config.instance.hostname;
      domain = domain-name;
      nameservers = site.nameservers;
      # This will cause a loop on the gateway itself
      #defaultGateway = site.gateway-v4;
      #defaultGateway6 = site.gateway-v6;

      firewall = {
        enable = (length host-cfg.external-interfaces) > 0;
        allowedTCPPorts = [ 22 ];
      };

      hostId = mkIf (host-cfg.machine-id != null)
        (substring 0 8 host-cfg.machine-id);
    };

    # NixOS generates a stupid hosts file, just force it
    environment.etc = {
      hosts = let
        host-entries = mapAttrsToList
          (ip: hostnames: "${ip} ${concatStringsSep " " hostnames}")
          config.fudo.system.hostfile-entries;
      in mkForce {
        text = ''
        127.0.0.1 ${hostname}.${domain-name} ${hostname} localhost
        127.0.0.2 ${hostname} localhost
        ::1 ${hostname}.${domain-name} ${hostname} localhost
        ${concatStringsSep "\n" host-entries}
      '';
        user = "root";
        group = "root";
        mode = "0444";
      };

      machine-id = mkIf (host-cfg.machine-id != null) {
        text = host-cfg.machine-id;
        user = "root";
        group = "root";
        mode = "0444";
      };

      current-system-packages.text = with builtins; let
        packages = map (p: "${p.name}")
          config.environment.systemPackages;
        sorted-unique = sort lessThan (unique packages);
      in concatStringsSep "\n" sorted-unique;
    };

    # fudo.hosts.${hostname}.build-pubkeys =
    #   map builtins.readFile
    #     (map (build-key-path: "${build-key-path}/${hostname}.key.pub")
    #       (optional (site.build-key-path != null) site.build-key-path));

    # nix = mkIf
    #   (has-build-servers && has-build-keys && site.enable-distributed-builds) {
    #     buildMachines = mapAttrsToList (hostname: buildOpts: {
    #       hostName = "${hostname}.${domain-name}";
    #       maxJobs = buildOpts.max-jobs;
    #       speedFactor = buildOpts.speed-factor;
    #       supportedFeatures = buildOpts.supported-features;
    #       sshKey = config.fudo.secrets.host-secrets.${hostname}.build-private-key.target-file;
    #     }) site.build-servers;
    #     distributedBuilds = true;
    #   };

    time.timeZone = site.timezone;

    krb5.libdefaults.default_realm = domain.gssapi-realm;

    services.cron.mailto = domain.admin-email;

    environment.systemPackages = with pkgs;
      mkIf (host-cfg.docker-server) [ docker nix-prefetch-docker ];

    virtualisation.docker = mkIf (host-cfg.docker-server) {
      enable = true;
      enableOnBoot = true;
      autoPrune.enable = true;
    };

    fudo = let
      try-attr = attr: set: if (hasAttr attr set) then set.${attr} else null;

      files = config.fudo.secrets.files;

      keytab-file = try-attr hostname files.host-keytabs;

      build-private-key-file =
        mapOptional
          (keypair: keypair.private-key)
          (try-attr hostname files.build-keypairs);

      backplane-passwd-source = try-attr hostname files.backplane-passwords;

      backplane-passwd-target = "/var/run/backplane/passwd";
    in {
      secrets.host-secrets.${hostname} = {
        host-keytab = mkIf (keytab-file != null) {
          source-file = keytab-file;
          target-file = "/etc/krb5.keytab";
          user = "root";
        };

        build-private-key = mkIf (build-private-key-file != null) {
          source-file = build-private-key-file;
          target-file = "/var/run/nix-build/host.key";
          user = "root";
        };

        backplane-passwd = mkIf (backplane-passwd-source != null) {
          source-file = backplane-passwd-source;
          target-file = backplane-passwd-target;
          user = config.fudo.client.dns.user;
        };
      };

      client.dns.password-file = mkIf (backplane-passwd-source != null)
        backplane-passwd-target;
    };

    programs.adb.enable = host-cfg.android-dev;
    users.groups.adbusers = mkIf host-cfg.android-dev {
      members = config.instance.local-admins;
    };

    boot.tmpOnTmpfs = host-cfg.tmp-on-tmpfs;

    # programs.ssh.knownHosts = let
    #   keyed-hosts =
    #     filterAttrs (host: opts: opts.ssh-pubkeys != []) config.fudo.hosts;

    #   crossProduct = f: list0: list1:
    #     concatMap (el0: map (el1: f el0 el1) list1) list0;

    #   getHostnames = hostOpts:
    #     [ hostOpts.hostname ]
    #     ++ (crossProduct (host: domain: "${host}.${domain}")
    #       ([ hostOpts.hostname ] ++ hostOpts.aliases)
    #       ([ hostOpts.domain ] ++ hostOpts.extra-domains));

    #   getHostEntryPairs = host:
    #     map (hostname: nameValuePair hostname { publicKey = host.ssh-pubkey; })
    #     (getHostnames host);

    #   hostAttrsToList = hostAttrs:
    #     mapAttrsToList (hostname: opts: { hostname = hostname; } // opts)
    #     hostAttrs;

    #   getKnownHosts = hosts:
    #     concatMap getHostEntryPairs (hostAttrsToList hosts);
    # in listToAttrs (getKnownHosts keyed-hosts);
  };
}