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

with lib;
let
  cfg = config;

  hydraPort = 3000;
  hydraAdmin = "admin";
  hydraAdminPasswordFile = "/run/keys/hydra-admin-password";

  createDeclarativeProjectScript = pkgs.stdenv.mkDerivation {
    name = "create-declarative-project";
    unpackPhase = ":";
    buildInputs = [ pkgs.makeWrapper ];
    installPhase = "install -m755 -D ${./create-declarative-project.sh} $out/bin/create-declarative-project";
    postFixup = ''
      wrapProgram "$out/bin/create-declarative-project" \
        --prefix PATH ":" ${pkgs.stdenv.lib.makeBinPath [ pkgs.curl ]}
    '';
  };

in
{
  options.services.hydra = {
    adminPasswordFile = mkOption {
      type = types.str;
      description = "The initial password for the Hydra admin account";
    };

    declarativeProjects = mkOption {
      description = "Declarative projects";
      default = { };
      type = with types; attrsOf (submodule {
        options = {
          inputValue = mkOption {
            type = types.str;
            description = "The input value";
            example = "https://github.com/shlevy/declarative-hydra-example";
          };
          inputType = mkOption {
            type = types.str;
            default = "git";
            description = "The type of the input value";
          };
          specFile = mkOption {
            type = types.str;
            default = "spec.json";
            description = "The declarative spec file name";
          };
          displayName = mkOption {
            type = types.str;
            description = "The diplay name of the declarative project";
          };
          description = mkOption {
            type = types.str;
            default = "";
            description = "The description of the declarative project";
          };
          homepage = mkOption {
            type = types.str;
            default = "";
            description = "The homepage of the declarative project";
          };
        };
      });
    };
  };
  config = {
    nixpkgs.config = {
      whitelistedLicenses = with lib.licenses; [
        unfreeRedistributable
        issl
      ];
      allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
        "cudnn_cudatoolkit"
        "cudatoolkit"
      ];
    };

    services.nginx.virtualHosts = {
      "hydra.nix-community.org" = {
        forceSSL = true;
        enableACME = true;
        locations."/" = {
          proxyPass = "http://localhost:${toString (hydraPort)}";
          extraConfig = ''
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
          '';
        };
      };
    };

    services.hydra = {
      enable = true;
      hydraURL = "hydra.nix-community.org";
      notificationSender = "hydra@hydra.nix-community.org";
      port = hydraPort;
      useSubstitutes = true;
      adminPasswordFile = hydraAdminPasswordFile;
      extraConfig = ''
        max_output_size = ${builtins.toString (8 * 1024 * 1024 * 1024)}
      '';
    };

    services.postgresql = {
      enable = true;
      settings = {
        effective_cache_size = "4GB";
        shared_buffers = "4GB";
      };
    };

    nix = {
      distributedBuilds = true;
      extraOptions = ''
        allowed-uris = https://github.com/nix-community/ https://github.com/NixOS/
      '';
      buildMachines = [
        {
          hostName = "localhost";
          systems = [ "x86_64-linux" "builtin" ];
          maxJobs = 8;
          supportedFeatures = [ "nixos-test" "big-parallel" "kvm" ];
        }
      ];
    };

    # Create a admin user and configure a declarative project
    systemd.services.hydra-post-init = mkIf (cfg.services.hydra.adminPasswordFile != null) {
      serviceConfig = {
        Type = "oneshot";
        TimeoutStartSec = "60";
      };
      wantedBy = [ "multi-user.target" ];
      after = [ "hydra-server.service" ];
      requires = [ "hydra-server.service" ];
      environment = {
        inherit (cfg.systemd.services.hydra-init.environment) HYDRA_DBI;
      };
      path = with pkgs; [ hydra-unstable netcat ];
      script = ''
        set -e
        export HYDRA_ADMIN_PASSWORD=$(cat ${cfg.services.hydra.adminPasswordFile})

        hydra-create-user ${hydraAdmin} --role admin --password $HYDRA_ADMIN_PASSWORD
        while ! nc -z localhost ${toString hydraPort}; do
          sleep 1
        done

        export URL=http://localhost:${toString hydraPort} 
      '' +
      (concatStringsSep "\n" (mapAttrsToList
        (n: v: ''
          export DECL_PROJECT_NAME="${n}"
          export DECL_DISPLAY_NAME="${v.displayName}"
          export DECL_VALUE="${v.inputValue}"
          export DECL_TYPE="${v.inputType}"
          export DECL_FILE="${v.specFile}"
          export DECL_DESCRIPTION="${v.description}"
          export DECL_HOMEPAGE="${v.homepage}"
          ${createDeclarativeProjectScript}/bin/create-declarative-project
        '')
        cfg.services.hydra.declarativeProjects));
    };
  };
}