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

with lib;

let
  portSpec = name: node: concatStringsSep "\n" (map (port: ''
    port ${name}.${port}
    type pipe
    protocol ${node.protocols}
    reliable true
    command ${pkgs.openssh}/bin/ssh -x -o batchmode=yes ${name}.${port}
  '') node.hostnames);
  sysSpec = name: node: ''
    system ${name}
    time any
    chat-seven-bit false
    chat . ""
    protocol ${node.protocols}
    command-path ${concatStringsSep " " cfg.commandPath}
    commands ${concatStringsSep " " node.commands}
    ${concatStringsSep "\nalternate\n" (map (port: ''
      port ${name}.${port}
    '') node.hostnames)}
  '';
  sshConfig = name: node: concatStringsSep "\n" (map (port: ''
    Host ${name}.${port}
      Hostname ${port}
      IdentitiesOnly Yes
      IdentityFile ${cfg.sshKeyDir}/${name}
  '') node.hostnames);
  sshKeyGen = name: node: ''
    if [[ ! -e ${cfg.sshKeyDir}/${name} ]]; then
      ${pkgs.openssh}/bin/ssh-keygen ${escapeShellArgs node.generateKey} -f ${cfg.sshKeyDir}/${name}
    fi
  '';
  restrictKey = key: ''
    restrict,command="${chat}" ${key}
  '';
  chat = pkgs.writeScript "chat" ''
    #!${pkgs.stdenv.shell}

    echo .
    exec ${config.security.wrapperDir}/uucico
  '';

  nodeCfg = {
    options = {
      commands = mkOption {
        type = types.listOf types.str;
        default = cfg.defaultCommands;
        description = "Commands to allow for this remote";
      };

      protocols = mkOption {
        type = types.separatedString "";
	      default = cfg.defaultProtocols;
	      description = "UUCP protocols to use for this remote";
      };

      publicKeys = mkOption {
        type = types.listOf types.str;
        default = [];
        description = "SSH client public keys for this node";
      };

      generateKey = mkOption {
        type = types.listOf types.str;
        default = [ "-t" "ed25519" "-N" "" ];
        description = "Arguments to pass to `ssh-keygen` to generate a keypair for communication with this host";
      };

      hostnames = mkOption {
        type = types.listOf types.str;
        default = [];
        description = "Hostnames to try in order when connecting";
      };
    };
  };

  cfg = config.services.uucp;
in {
  options = {
    services.uucp = {
      enable = mkOption {
        type = types.bool;
        default = false;
        description = ''
          If enabled we set up an account accesible via uucp over ssh
        '';
      };

      nodeName = mkOption {
        type = types.str;
        default = "nixos";
        description = "uucp node name";
      };

      sshUser = mkOption { 
        type = types.attrs;
        default = {};
        description = "Overrides for the local uucp linux-user";
      };

      extraSSHConfig = mkOption {
        type = types.str;
        default = "";
        description = "Extra SSH config";
      };

      remoteNodes = mkOption {
        type = types.attrsOf (types.submodule nodeCfg);
        default = {};
        description = ''
          Ports to set up
          Names will probably need to be configured in sshConfig
        '';
      };

      commandPath = mkOption {
        type = types.listOf types.path;
        default = [ "${pkgs.rmail}/bin" ];
        description = ''
          Command search path for all systems
        '';
      };

      defaultCommands = mkOption {
        type = types.listOf types.str;
        default = ["rmail"];
        description = "Commands allowed for remotes without explicit override";
      };

      defaultProtocols = mkOption {
        type = types.separatedString "";
	      default = "te";
	      description = "UUCP protocol to use within ssh unless overriden";
      };

      incomingProtocols = mkOption {
        type = types.separatedString "";
        default = "te";
        description = "UUCP protocols to use when called";
      };

      homeDir = mkOption {
        type = types.path;
        default = "/var/uucp";
        description = "Home of the uucp user";
      };

      sshKeyDir = mkOption {
        type = types.path;
        default = "${cfg.homeDir}/.ssh/";
        description = "Directory to store ssh keypairs";
      };

      spoolDir = mkOption {
        type = types.path;
        default = "/var/spool/uucp";
        description = "Spool directory";
      };

      lockDir = mkOption {
        type = types.path;
        default = "/var/spool/uucp";
        description = "Lock directory";
      };

      pubDir = mkOption {
        type = types.path;
        default = "/var/spool/uucppublic";
        description = "Public directory";
      };

      logFile = mkOption {
        type = types.path;
        default = "/var/log/uucp";
        description = "Log file";
      };

      statFile = mkOption {
        type = types.path;
        default = "/var/log/uucp.stat";
        description = "Statistics file";
      };

      debugFile = mkOption {
        type = types.path;
        default = "/var/log/uucp.debug";
        description = "Debug file";
      };

      interval = mkOption {
        type = types.nullOr types.str;
        default = "1h";
        description = ''
          Specification of when to run `uucico' in format used by systemd timers
          The default is to do so every hour
        '';
      };

      nmDispatch = mkOption {
        type = types.bool;
        default = config.networking.networkmanager.enable;
        description = ''
          Install a network-manager dispatcher script to automatically
          call all remotes when networking is available
        '';
      };

      extraConfig = mkOption {
        type = types.lines;
        default = ''
          run-uuxqt 1
        '';
        description = "Extra configuration to append verbatim to `/etc/uucp/config'";
      };

      extraSys = mkOption {
        type = types.lines;
        default = ''
          protocol-parameter g packet-size 4096
        '';
	      description = "Extra configuration to prepend verbatim to `/etc/uucp/sys`";
      };
    };
  };

  config = mkIf cfg.enable {
    environment.etc."uucp/config" = {
      text = ''
        hostname ${cfg.nodeName}
      
        spool ${cfg.spoolDir}
        lockdir ${cfg.lockDir}
        pubdir ${cfg.pubDir}
        logfile ${cfg.logFile}
        statfile ${cfg.statFile}
        debugfile ${cfg.debugFile}
        
        ${cfg.extraConfig}
      '';
    };

    users.groups."uucp" = {};
    users.users."uucp" = {
      name = "uucp";
      group = "uucp";
      isSystemUser = true;
      isNormalUser = false;
      createHome = true;
      home = cfg.homeDir;
      description = "User for uucp over ssh";
      useDefaultShell = true;
      openssh.authorizedKeys.keys = map restrictKey (concatLists (mapAttrsToList (name: node: node.publicKeys) cfg.remoteNodes));
    } // cfg.sshUser;

    system.activationScripts."uucp-sshconfig" = ''
      mkdir -p ${config.users.users."uucp".home}/.ssh
      chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${config.users.users."uucp".home}/.ssh
      chmod 700 ${config.users.users."uucp".home}/.ssh
      ln -fs ${builtins.toFile "ssh-config" ''
        ${concatStringsSep "\n" (mapAttrsToList sshConfig cfg.remoteNodes)}
      
        ${cfg.extraSSHConfig}
      ''} ${config.users.users."uucp".home}/.ssh/config

      mkdir -p ${cfg.sshKeyDir}
      chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.sshKeyDir}
      chmod 700 ${cfg.sshKeyDir}

      ${concatStringsSep "\n" (mapAttrsToList sshKeyGen cfg.remoteNodes)}
    '';

    system.activationScripts."uucp-logs" = ''
      touch ${cfg.logFile}
      chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.logFile}
      chmod 644 ${cfg.logFile}
      touch ${cfg.statFile}
      chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.statFile}
      chmod 644 ${cfg.statFile}
      touch ${cfg.debugFile}
      chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.debugFile}
      chmod 644 ${cfg.debugFile}
    '';

    environment.etc."uucp/port" = {
      text = ''
        port ssh
        type stdin
        protocol ${cfg.incomingProtocols}
      '' + concatStringsSep "\n" (mapAttrsToList portSpec cfg.remoteNodes);
    };
    environment.etc."uucp/sys" = {
      text = cfg.extraSys + "\n" + concatStringsSep "\n" (mapAttrsToList sysSpec cfg.remoteNodes);
    };

    security.wrappers = let
      wrapper = p: {
        name = p;
        value = {
          source = "${pkgs.uucp}/bin/${p}";
          owner = "root";
          group = "root";
          setuid = true;
          setgid = false;
        };
      };
    in listToAttrs (map wrapper ["uucico" "cu" "uucp" "uuname" "uustat" "uux" "uuxqt"]);

    nixpkgs.overlays = [(self: super: {
      uucp = super.lib.overrideDerivation super.uucp (oldAttrs: {
        configureFlags = "--with-newconfigdir=/etc/uucp";
        patches = [
          (super.writeText "mailprogram" ''
             policy.h | 2 +-
             1 file changed, 1 insertion(+), 1 deletion(-)

            diff --git a/policy.h b/policy.h
            index 5afe34b..8e92c8b 100644
            --- a/policy.h
            +++ b/policy.h
            @@ -240,7 +240,7 @@
                the sendmail choice below.  Otherwise, select one of the other
                choices as appropriate.  */
             #if 1
            -#define MAIL_PROGRAM "/usr/lib/sendmail -t"
            +#define MAIL_PROGRAM "${config.security.wrapperDir}/sendmail -t"
             /* #define MAIL_PROGRAM "/usr/sbin/sendmail -t" */
             #define MAIL_PROGRAM_TO_BODY 1
             #define MAIL_PROGRAM_SUBJECT_BODY 1
          '')
        ];
      });
      rmail = super.writeScriptBin "rmail" ''
          #!${super.stdenv.shell}

          # Dummy UUCP rmail command for postfix/qmail systems

          IFS=" " read junk from junk junk junk junk junk junk junk relay

          case "$from" in
            *[@!]*) ;;
            *) from="$from@$relay";;
          esac

          exec ${config.security.wrapperDir}/sendmail -G -i -f "$from" -- "$@"
        '';
    })];

    environment.systemPackages = with pkgs; [
      uucp
    ];

    systemd.services."uucico@" = {
      serviceConfig = {
        User = "uucp";
        Type = "oneshot";
        ExecStart = "${config.security.wrapperDir}/uucico -D -S %i";
      };
    };

    systemd.timers."uucico@" = {
      timerConfig.OnActiveSec = cfg.interval;
      timerConfig.OnUnitActiveSec = cfg.interval;
    };

    systemd.targets."multi-user" = {
      wants = mapAttrsToList (name: node: "uucico@${name}.timer") cfg.remoteNodes;
    };

    systemd.kill-user.enable = true;
    systemd.targets."sleep" = {
      after = [ "kill-user@uucp.service" ];
      wants = [ "kill-user@uucp.service" ];
    };
    
    networking.networkmanager.dispatcherScripts = optional cfg.nmDispatch {
      type = "basic";
      source = pkgs.writeScript "callRemotes.sh" ''
        #!${pkgs.stdenv.shell}

        shopt -s extglob

        case "''${2}" in
          (?(vpn-)up)
            ${concatStringsSep "\n    " (mapAttrsToList (name: node: "${pkgs.systemd}/bin/systemctl start uucico@${name}.service") cfg.remoteNodes)}
            ;;
        esac
      '';
    };
  };
}