{
  config,
  pkgs,
  lib,
  inputs,
  ...
}:
with lib; let
  name = "email";
  cfg = config.services.skynet."${name}";

  # create teh new strings
  create_filter_array = map (x: "(memberOf=cn=${x},ou=groups,${cfg.ldap.base})");

  create_filter_join = x: concatStringsSep "" x;

  # thought you could escape racket?
  create_filter = groups: create_filter_join (create_filter_array groups);

  # using +mailbox puts the mail in a seperate folder
  create_skynet_email_int = accounts: mailbox: (map (account: "${account}@skynet.ie") accounts);
  groups_to_accounts = groups: builtins.concatMap (x: config.skynet.users.${x}) groups;
  create_skynet_email_attribute = mailbox: groups: (create_skynet_email_int (groups_to_accounts groups) mailbox) ++ ["int_${mailbox}@skynet.ie"];
  create_skynet_email = mailbox: groups: {
    name = "${mailbox}@skynet.ie";
    value = create_skynet_email_attribute mailbox groups;
  };
  create_skynet_service_mailboxes = builtins.listToAttrs (map (mailbox: (create_skynet_email mailbox.account mailbox.members)) service_mailboxes);

  create_config_to = concatStringsSep "\",\"" (map (mailbox: "${mailbox.account}") service_mailboxes);

  service_mailboxes = [
    {
      account = "root";
      members = ["admin"];
    }
    {
      account = "abuse";
      members = ["admin"];
    }
    {
      account = "accounts";
      members = ["committee"];
    }
    {
      account = "compsoc";
      members = ["committee"];
    }
    {
      account = "contact";
      members = ["committee"];
    }
    {
      account = "dbadmin";
      members = ["admin"];
    }
    {
      account = "dnsadm";
      members = ["admin"];
    }
    {
      account = "hostmaster";
      members = ["admin"];
    }
    {
      account = "intersocsrep";
      members = ["committee"];
    }
    {
      account = "mailman";
      members = ["admin"];
    }
    {
      account = "security";
      members = ["admin"];
    }
    {
      account = "sysadm";
      members = ["admin"];
    }
    {
      account = "webadmin";
      members = ["admin"];
    }
    {
      account = "pycon2023";
      members = ["committee"];
    }
    {
      account = "skynet_topdesk";
      members = ["admin" "trainee"];
    }
  ];

  sieveConfigFile =
    # https://doc.dovecot.org/configuration_manual/sieve/examples/#plus-addressed-mail-filtering
    pkgs.writeText "basic_sieve"
    ''
      require "copy";
      require "mailbox";
      require "imap4flags";
      require ["fileinto", "reject"];
      require "variables";
      require "regex";

      # this should be close to teh last step
      if allof (
        address  :localpart ["To", "Cc"] ["${toString create_config_to}"],
        address  :domain ["To", "Cc"] "skynet.ie"
        ){
        if address :matches ["To", "Cc"] "*@skynet.ie" {
          if header :is "X-Spam" "Yes" {
            fileinto :create "''${1}.Junk";
            stop;
          } else {
            fileinto :create "''${1}";
            stop;
          }
        }
      }
      if allof (
        address  :localpart ["From"] ["${toString create_config_to}"],
        address  :domain ["From"] "skynet.ie"
        ){
        if address :matches ["From"] "*@skynet.ie" {
          if header :is "X-Spam" "Yes" {
            fileinto :create "''${1}.Junk";
            stop;
          } else {
            fileinto :create "''${1}";
            stop;
          }
        }
      }
    '';
in {
  imports = [
    inputs.simple-nixos-mailserver.nixosModule

    # for teh config
    ../config/users.nix
  ];

  options.services.skynet."${name}" = {
    # options that need to be passed in to make this work

    enable = mkEnableOption "Skynet Email";

    domain = mkOption {
      type = types.str;
      default = "skynet.ie";
      description = lib.mdDoc "domaino";
    };

    sub = mkOption {
      type = types.str;
      default = "mail";
      description = lib.mdDoc "mailserver subdomain";
    };

    groups = mkOption {
      type = types.listOf types.str;
      default = [
        # general skynet users
        "skynet-users"
        # C&S folsk get access
        "skynet-cns"
        # skynet service accounts
        "skynet-service"
      ];
      description = lib.mdDoc "Groups we want to allow access to the email";
    };

    ldap = {
      hosts = mkOption {
        type = types.listOf types.str;
        default = [
          "ldaps://account.skynet.ie"
        ];
        description = lib.mdDoc "ldap domains";
      };

      base = mkOption {
        type = types.str;
        default = "dc=skynet,dc=ie";
        description = lib.mdDoc "where to find users";
      };

      searchBase = mkOption {
        type = types.str;
        default = "ou=users,${cfg.ldap.base}";
        description = lib.mdDoc "where to find users";
      };

      bind_dn = mkOption {
        type = types.str;
        default = "cn=admin,${cfg.ldap.base}";
        description = lib.mdDoc "where to find users";
      };
    };
  };

  config = mkIf cfg.enable {
    services.skynet.backup.normal.backups = [
      #"/var/vmail"
      "/var/dkim"
    ];

    age.secrets.ldap_pw.file = ../secrets/ldap/pw.age;

    security.acme.certs = {
      "mail" = {
        domain = "mail.skynet.ie";
        extraDomainNames = [
          "imap.skynet.ie"
          "pop3.skynet.ie"
          "smtp.skynet.ie"
        ];
      };

      "imap" = {
        domain = "imap.skynet.ie";
        extraDomainNames = [
          "mail.skynet.ie"
          "pop3.skynet.ie"
          "smtp.skynet.ie"
        ];
      };

      "pop3" = {
        domain = "pop3.skynet.ie";
        extraDomainNames = [
          "imap.skynet.ie"
          "mail.skynet.ie"
          "smtp.skynet.ie"
        ];
      };

      "smtp" = {
        domain = "smtp.skynet.ie";
        extraDomainNames = [
          "imap.skynet.ie"
          "pop3.skynet.ie"
          "mail.skynet.ie"
        ];
      };
    };

    # to provide the certs
    services.nginx.virtualHosts = {
      "mail.skynet.ie" = {
        forceSSL = true;
        useACMEHost = "mail";
        # override the inbuilt nginx config
        enableACME = false;
        serverName = "mail.skynet.ie";
      };

      "imap.skynet.ie" = {
        forceSSL = true;
        useACMEHost = "imap";
        # override the inbuilt nginx config
        enableACME = false;
        serverName = "imap.skynet.ie";
      };

      "pop3.skynet.ie" = {
        forceSSL = true;
        useACMEHost = "pop3";
        # override the inbuilt nginx config
        enableACME = false;
        serverName = "pop3.skynet.ie";
      };

      "smtp.skynet.ie" = {
        forceSSL = true;
        useACMEHost = "smtp";
        # override the inbuilt nginx config
        enableACME = false;
        serverName = "smtp.skynet.ie";
      };
    };

    # set up dns record for it
    services.skynet.dns.records =
      [
        # core record
        {
          record = "@";
          r_type = "MX";
          # the number is the priority in teh case of multiple mailservers
          value = "10 mail.${cfg.domain}.";
        }

        # basic one
        {
          record = "mail";
          r_type = "A";
          value = config.services.skynet.host.ip;
        }
        #DNS config for K-9 Mail
        {
          record = "imap";
          r_type = "CNAME";
          value = "mail";
        }
        {
          record = "pop3";
          r_type = "CNAME";
          value = "mail";
        }
        {
          record = "smtp";
          r_type = "CNAME";
          value = "mail";
        }

        # TXT records, all tehse are inside escaped strings to allow using ""

        # reverse pointer
        {
          record = config.services.skynet.host.ip;
          r_type = "PTR";
          value = "${cfg.sub}.${cfg.domain}.";
        }

        # SRV records to help gmail on android etc find the correct mail.skynet.ie domain for config rather than just defaulting to skynet.ie
        # https://serverfault.com/questions/935192/how-to-setup-auto-configure-email-for-android-mail-app-on-your-server/1018406#1018406
        # response should be:
        # _imap._tcp       SRV  0 1 143 imap.example.com.
        {
          record = "_imaps._tcp";
          r_type = "SRV";
          value = "0 1 993 ${cfg.sub}.${cfg.domain}.";
        }
        {
          record = "_imap._tcp";
          r_type = "SRV";
          value = "0 1 143 ${cfg.sub}.${cfg.domain}.";
        }
        {
          record = "_submissions._tcp";
          r_type = "SRV";
          value = "0 1 465 ${cfg.sub}.${cfg.domain}.";
        }
        {
          record = "_submission._tcp";
          r_type = "SRV";
          value = "0 1 587 ${cfg.sub}.${cfg.domain}.";
        }
      ]
      # SPF record
      ++ [
        {
          record = "${cfg.domain}.";
          r_type = "TXT";
          value = ''"v=spf1 a:${cfg.sub}.${cfg.domain} ip4:${config.services.skynet.host.ip} -all"'';
        }
      ]
      # DKIM keys
      ++ [
        {
          record = "mail._domainkey.skynet.ie.";
          r_type = "TXT";
          value = ''"v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxju1Ie60BdHwyFVPNQKovL/cX9IFPzBKgjnHZf+WBzDCFKSBpf7NvnfXajtFDQN0poaN/Qfifid+V55ZCNDBn8Y3qZa4Y69iNiLw2DdvYf0HdnxX6+pLpbmj7tikGGLJ62xnhkJhoELnz5gCOhpyoiv0tSQVaJpaGZmoll861/QIDAQAB"'';
        }
        {
          domain = "ulcompsoc.ie";
          record = "mail._domainkey.ulcompsoc.ie.";
          r_type = "TXT";
          value = ''"v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDl8ptSASx37t5sfmU2d2Y6yi9AVrsNFBZDmJ2uaLa4NuvAjxGQCw4wx+1Jui/HOuKYLpntLsjN851wgPR+3i51g4OblqBDvcHn9NYgWRZfHj9AASANQjdsaAbkXuyKuO46hZqeWlpESAcD6a4Evam4fkm+kiZC0+rccb4cWgsuLwIDAQAB"'';
        }
      ]
      # DMARC
      ++ [
        {
          record = "_dmarc.${cfg.domain}.";
          r_type = "TXT";
          # p     : quarantine => sends to spam, reject => never sent
          # rua   : mail that receives reports about DMARC activity
          # pct   : percentage of unathenticated messages that DMARC stops
          # adkim : alignment policy for DKIM, s => Strict, subdomains arent allowed, r => relaxed, subdomains allowed
          # aspf  : alignment policy for SPF, s => Strict, subdomains arent allowed, r => relaxed, subdomains allowed
          # sp    : DMARC policy for subdomains, none => no action, reports to rua, quarantine => spam, reject => never sent
          value = ''"v=DMARC1; p=quarantine; rua=mailto:mailman@skynet.ie; pct=100; adkim=s; aspf=s; sp=quarantine"'';
        }
      ];

    #https://nixos-mailserver.readthedocs.io/en/latest/add-roundcube.html
    users.groups.nginx = {};
    users.groups.roundcube = {};
    services.roundcube = {
      enable = true;
      # this is the url of the vhost, not necessarily the same as the fqdn of
      # the mailserver
      hostName = "${cfg.sub}.${cfg.domain}";
      extraConfig = ''
        # starttls needed for authentication, so the fqdn required to match
        # the certificate
        $config['smtp_server'] = "ssl://${cfg.sub}.${cfg.domain}";
        $config['smtp_user'] = "%u";
        $config['smtp_pass'] = "%p";
        $config['imap_host'] =  "ssl://${cfg.sub}.${cfg.domain}";
        $config['product_name'] = "Skynet Webmail";
        $config['identities_level'] = 4;
        $config['login_username_filter'] = "email";
        $config['ldap_public']['public'] = array(
          'name'              => 'Public LDAP Addressbook',
          'hosts'             => 'tls://account.skynet.ie',
          'port'              => 636 ,
          'user_specific'     => false,
          'base_dn'           => 'ou=users,dc=skynet,dc=ie',
          'filter'            => '(skMemberOf=cn=skynet-users-linux,ou=groups,dc=skynet,dc=ie)',
          'fieldmap' => [
            // Roundcube  => LDAP:limit
              'name'        => 'cn',
              'surname'     => 'sn',
              'email'       => 'skMail:*',
            ]
        );
      '';
    };

    # for https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/issues/275
    services.dovecot2.sieve.extensions = ["fileinto"];

    mailserver = {
      enable = true;
      fqdn = "${cfg.sub}.${cfg.domain}";
      domains = [
        cfg.domain
      ];

      enableManageSieve = true;

      lmtpSaveToDetailMailbox = "yes";

      extraVirtualAliases = create_skynet_service_mailboxes;

      # use the letsencrypt certs
      certificateScheme = "acme";

      # 20MB max size
      messageSizeLimit = 20000000;

      ldap = {
        enable = true;
        uris = cfg.ldap.hosts;
        bind = {
          dn = cfg.ldap.bind_dn;
          passwordFile = config.age.secrets.ldap_pw.path;
        };

        searchBase = cfg.ldap.searchBase;
        searchScope = "sub";

        dovecot = {
          userFilter = "(skMail=%u)";

          # can lock down how much space each user has access to from ldap
          userAttrs = "quotaEmail=quota_rule=*:bytes=%$,=quota_rule2=Trash:storage=+100M";

          # accept emails in, but only allow access to paid up members
          passFilter = "(&(|${create_filter cfg.groups})(skMail=%u))";
        };

        postfix = {
          filter = "(|(skMail=%s)(uid=%s))";
          uidAttribute = "skMail";
          mailAttribute = "skMail";
        };
      };

      # feckin spammers
      rejectRecipients = [
      ];
    };

    services.dovecot2.sieve.scripts = {
      before = sieveConfigFile;
    };

    # This is to add a bcc to outgoing mail
    # this then interacts with teh filters to put it in the right folder
    # we can directly add to the postfix service here
    services.postfix = let
      # mostly copied from the upstream mailserver config/functions
      mappedFile = name: "hash:/var/lib/postfix/conf/${name}";

      sender_bcc_maps_file = let
        content = lookupTableToString create_skynet_service_bcc;
      in
        builtins.toFile "sender_bcc_maps" content;

      lookupTableToString = attrs: let
        valueToString = value: lib.concatStringsSep ", " value;
      in
        lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs);

      # convert the mailboxes config to something that can be used here
      create_skynet_email_bcc = mailbox: {
        name = "${mailbox}@skynet.ie";
        value = ["${mailbox}@skynet.ie"];
      };
      create_skynet_service_bcc = builtins.listToAttrs (map (mailbox: (create_skynet_email_bcc mailbox.account)) service_mailboxes);
    in {
      mapFiles."sender_bcc_maps" = sender_bcc_maps_file;

      config = {
        sender_bcc_maps = [
          (mappedFile "sender_bcc_maps")
        ];
      };
    };

    # tune the spam filter
    /*
    services.rspamd.extraConfig = ''
      actions {
        reject = null; # Disable rejects, default is 15
        add_header = 7; # Add header when reaching this score
        greylist = 4; # Apply greylisting when reaching this score
      }
    '';
    */
  };
}