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

let
  cfg = config.services.kukkee;
in with builtins; {
  options.services.kukkee = with lib; {

    enable = mkEnableOption "kukkee service";

    package = mkPackageOption pkgs "kukkee" { };

    user = mkOption {
      type = types.str;
      default = "kukkee";
      description = "User under which Kukkee runs.";
    };

    group = mkOption {
      type = types.str;
      default = "kukkee";
      description = "Group under which Kukkee runs.";
    };

    listen = mkOption {
      type = types.str;
      default = "127.0.0.1";
      description = "Which address to listen on.";
    };

    port = mkOption {
      type = types.port;
      default = 3000;
      description = "Which port Kukkee should listen to for HTTP.";
    };

    extraArgs = mkOption {
      type = types.listOf types.str;
      default = [];
      description = "Extra command-line arguments for the next.js runtime.";
    };

    baseUrl = mkOption {
      type = types.str;
      default = "http://localhost:${cfg.port}";
      description = "The base URL for the site";
    };

    openFirewall = mkOption {
      type = types.bool;
      default = false;
      description = ''
        Open the configured port in the firewall for the Kukkee server.
        Preferably the Kukkee server is instead put behind a reverse proxy.
      '';
    };

    mongodb.enable = mkOption {
      type = types.bool;
      default = true;
      description = "Whether to configure a local MongoDB instance.";
    };

    mongodb.uri = mkOption {
      type = types.str;
      default = "mongodb://127.0.0.1:27017/kukkeePolls";
      example = "mongodb+srv://<user>:<password>@<cluster>.aewjs.mongodb.net/<database>?retryWrites=true&w=majority";
      description = ''
        Mongodb connection string. MongoDB databases are normally
        created automatically upon first write.
      '';
    };

  };

  config = lib.mkMerge [
    (lib.mkIf (cfg.enable && cfg.mongodb.enable) {
      services.mongodb.enable = true;
      systemd.services.kukkee.after = [ "mongodb.service" ];
      systemd.services.kukkee.requires = [ "mongodb.service" ];
    })
    (lib.mkIf cfg.enable {
      systemd.services.kukkee = {
        description = "Kukkee Server";
        after = [ "network.target" ];
        wantedBy = [ "multi-user.target" ];

        environment = {
          # https://github.com/AnandBaburajan/Kukkee/blob/270c8ed421c8f1100a845958430e1ebe61d86d5a/.env.example
          NEXT_MONGODB_URI = cfg.mongodb.uri;
          NEXT_PUBLIC_BASE_URL = cfg.baseUrl;
          NEXT_PUBLIC_ENCRYPTION_KEY = "2a148b84dcec756c59ab96d450a79372"; # TODO
          NEXT_PUBLIC_ENCRYPTION_IV = "0d88ec0887f614b6"; # TODO
        };

        serviceConfig = let
          args = map lib.strings.escapeShellArg [
            "--hostname" cfg.listen
            "--port" cfg.port
          ] ++ cfg.extraArgs;
        in rec {
          User = cfg.user;
          Group = cfg.group;
          ExecStart = "${cfg.package}/bin/kukkee " + (lib.strings.concatStringsSep " " args);
          Restart = "on-failure";

          # Security options:

          NoNewPrivileges = true;
          AmbientCapabilities = "";
          CapabilityBoundingSet = "";

          DeviceAllow = "";
          LockPersonality = true;
          PrivateTmp = true;
          PrivateDevices = true;
          PrivateUsers = true;

          ProtectClock = true;
          ProtectControlGroups = true;
          ProtectHostname = true;
          ProtectKernelLogs = true;
          ProtectKernelModules = true;
          ProtectKernelTunables = true;

          RemoveIPC = true;

          RestrictNamespaces = true;
          RestrictAddressFamilies = [ "AF_NETLINK" "AF_INET" "AF_INET6" "AF_UNIX" ];
          RestrictRealtime = true;
          RestrictSUIDSGID = true;

          SystemCallArchitectures = "native";
          SystemCallErrorNumber = "EPERM";
          SystemCallFilter = [
            "@system-service"
            "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"
          ];
        };
      };

      users.users = lib.mkIf (cfg.user == "kukkee") {
        kukkee = {
          group = cfg.group;
          isSystemUser = true;
        };
      };

      users.groups = lib.mkIf (cfg.group == "kukkee") {
        kukkee = {};
      };

      networking.firewall = lib.mkIf cfg.openFirewall {
        allowedTCPPorts = [ cfg.port ];
      };
    })
  ];

  meta.maintainers = with lib.maintainers; [ pbsds ];
}

