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

with lib;

let
  mailman3 = import ./release.nix { };
  cfg = config.services.mailman3;
  usePostgresql = cfg.database.type == "postgresql";
  useSqlite = cfg.database.type == "sqlite3";
  useMysql = cfg.database.type == "mysql";
  usePostfix = cfg.mta.type == "postfix";
  configFile_postfix = pkgs.writeText "postfix.cfg" ''
    [postfix]
    transport_file_type: hash
    postmap_command: ${pkgs.postfix}/bin/postmap
  '';
  configFile = pkgs.writeText "mailman.cfg" ''
    # This File was automatically generated by the mailman3 nixos submodule.
    # Do not manually edit this file.

    [mailman]
    site_owner: ${cfg.site_owner}
    layout: custom

    [paths.custom]
    archive_dir: ${cfg.paths.archive_dir}
    bin_dir: ${cfg.paths.bin_dir}
    cache_dir: ${cfg.paths.cache_dir}
    data_dir: ${cfg.paths.data_dir}
    etc_dir: ${cfg.paths.etc_dir}
    lock_dir: ${cfg.paths.lock_dir}
    log_dir: ${cfg.paths.log_dir}
    messages_dir: ${cfg.paths.messages_dir}
    template_dir: ${cfg.paths.template_dir}
    var_dir: ${cfg.paths.var_dir}
    lock_file: ${cfg.paths.lock_file}
    pid_file: ${cfg.paths.pid_file}

    # The Database Documentation can be found here:
    # https://mailman.readthedocs.io/en/latest/src/mailman/docs/database.html
    # https://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls
    [database]
    ${optionalString useSqlite ''
      [database]
      url: sqlite:///${cfg.database.socket}
    ''}
    ${optionalString usePostgresql ''
      class: mailman.database.postgresql.PostgreSQLDatabase
      url: postgres://${cfg.database.user}:#dbpass#@${database.host}:${toString cfg.database.port}/${cfg.database.name}
    ''}
    ${optionalString useMysql ''
      class: mailman.database.mysql.MySQLDatabase
      url: mysql+pymysql://${cfg.database.user}:#dbpass#@${cfg.database.host}:${toString cfg.database.port}/${cfg.database.name}?charset=utf8&use_unicode=1
    ''}

    [mta]
    incoming: ${cfg.mta.incoming}
    outgoing: ${cfg.mta.outgoing}
    lmtp_host: ${cfg.mta.lmtp_host}
    lmtp_port: ${toString cfg.mta.lmtp_port}
    smtp_host: ${cfg.mta.smtp_host}
    smtp_port: ${toString cfg.mta.smtp_port}
    smtp_user: ${cfg.mta.smtp_user}
    smtp_pass: #smtppass#
    # One of smtp/smtps/starttls, specifies the protocol Mailman will use when
    # connecting.  Typically will correspond to smtp_port: 25 -> smtp, 465 -> smtps,
    # 587 -> starttls.
    smtp_secure_mode: smtp
    configuration: ${configFile_postfix}

    [ARC]
    enabled: no

    ${cfg.extraConfig}
  '';
in {
  options.services.mailman3 = {
    enable = mkOption {
      type = types.bool;
      default = false;
      example = true;
      description = ''
        Wether to enable mailman3 mailing list management system (core).
      '';
    };

    site_owner = mkOption {
      type = types.str;
      default = "changeme@example.com";
      description = ''
        mailman3 site owner address.
      '';
    };

    user = mkOption {
      type = types.str;
      default = "mailman3";
      description = ''
        User account under which mailman runs.
      '';
    };

    database = {
      type = mkOption {
        type = types.enum [ "sqlite3" "mysql" "postgres" ];
        default = "sqlite3";
        example = "mysql";
        description = ''
          Database type to use.
        '';
      };

      host = mkOption {
        type = types.str;
        default = "172.0.0.1";
        description = ''
          Database host adress.
        '';
      };

      port = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = ''

        '';
      };

      name = mkOption {
        type = types.str;
        default = "mailman3";
        description = ''
          Database name.
        '';
      };

      user = mkOption {
        type = types.str;
        default = "mailman3";
        description = ''
          Database user.
        '';
      };

      password = mkOption {
        type = types.str;
        default = "";
        description = ''
          The password corresponding to <option>database.user</option>.
          Warning: this is stored in cleartext in the Nix store!
          Use <option>database.passwordFile</option> instead.
        '';
      };

      passwordFile = mkOption {
        type = types.nullOr types.path;
        default = null;
        example = "/run/keys/mailman3-dbpassword";
        description = ''
          A file containing the password corresponding to
          <option>database.user</option>.
        '';
      };

      socket = mkOption {
        type = types.nullOr types.path;
        default = null;
        example = "/run/mysqld/mysqld.sock";
        description = "Path to the unix socket file to use for authentication.";
      };
    };

    paths = {
      archive_dir = mkOption {
        type = types.str;
        default = "${cfg.paths.var_dir}/archives";
        description = ''

        '';
      };

      bin_dir = mkOption {
        type = types.str;
        default = "${mailman3.core}/bin";
        description = ''

        '';
      };

      cache_dir = mkOption {
        type = types.str;
        default = "${cfg.paths.var_dir}/cache";
        description = ''

        '';
      };

      data_dir = mkOption {
        type = types.str;
        default = "${cfg.paths.var_dir}/data";
        description = ''

        '';
      };

      etc_dir = mkOption {
        type = types.str;
        default = "${cfg.paths.var_dir}/etc";
        description = ''

        '';
      };

      lock_dir = mkOption {
        type = types.str;
        default = "${cfg.paths.var_dir}/locks";
        description = ''

        '';
      };

      log_dir = mkOption {
        type = types.str;
        default = "${cfg.paths.var_dir}/logs";
        description = ''

        '';
      };

      messages_dir = mkOption {
        type = types.str;
        default = "${cfg.paths.var_dir}/messages";
        description = ''

        '';
      };

      template_dir = mkOption {
        type = types.str;
        default = "${cfg.paths.var_dir}/templates";
        description = ''

        '';
      };

      var_dir = mkOption {
        type = types.str;
        default = "/var/lib/mailman3";
        description = ''

        '';
      };

      lock_file = mkOption {
        type = types.path;
        default = "${cfg.paths.lock_dir}/master.lck";
        description = ''

        '';
      };

      pid_file = mkOption {
        type = types.path;
        default = "${cfg.paths.var_dir}/master.pid";
        description = ''

        '';
      };
    };

    mta = {
      # TODO: add Sentmail and qmail
      type = mkOption {
        type = types.enum [ "postfix" "exim4" ];
        default = "postfix";
        example = "exim4";
        description = ''
          Database type to use.
        '';
      };
      incoming = mkOption {
        type = types.str;
        default = "mailman.mta.${cfg.mta.type}.LMTP";
        description = ''

        '';
      };
      outgoing = mkOption {
        type = types.str;
        default = "mailman.mta.deliver.deliver";
        description = ''

        '';
      };
      lmtp_host = mkOption {
        type = types.str;
        default = "mail.example.com";
        description = ''

        '';
      };
      lmtp_port = mkOption {
        type = types.int;
        default = 8024;
        description = ''

        '';
      };
      smtp_host = mkOption {
        type = types.str;
        default = "mail.example.com";
        description = ''

        '';
      };
      smtp_port = mkOption {
        type = types.int;
        default = 25;
        description = ''

        '';
      };
      smtp_user = mkOption {
        type = types.str;
        default = "";
        description = ''

        '';
      };
      smtp_pass = mkOption {
        type = types.str;
        default = "";
        description = ''

        '';
      };
      smtp_passFile = mkOption {
        type = types.str;
        default = "";
        description = ''

        '';
      };
    };

    extraConfig = mkOption {
      type = types.str;
      default = "";
      example = ''
        [devmode]
        enabled: yes
        recipient: your.address@your.domain
      '';
      description = ''
        Configuration lines appended to the generated mailman3 configuration file.
      '';
    };
  };


  config = mkIf cfg.enable {
    users = mkIf (cfg.user == "mailman3") {
      users.mailman3 = {
        description = "Mailman3 Service";
        home = cfg.paths.var_dir;
        createHome = true;
        useDefaultShell = true;
        packages = [ mailman3.core ];
      };
    };

    services.postfix.recipientDelimiter = mkIf usePostfix (mkDefault "+");
    services.postfix.mapFiles."transport_maps" = mkIf usePostfix (mkDefault "${cfg.paths.data_dir}/postfix_lmtp");
    services.postfix.mapFiles."local_recipient_maps" = mkIf usePostfix (mkDefault "${cfg.paths.data_dir}/postfix_lmtp");
    services.postfix.mapFiles."relay_domains" = mkIf usePostfix (mkDefault "${cfg.paths.data_dir}/postfix_domains");

    warnings = optional (cfg.database.password != "")
      ''config.services.mailman3.database.password will be stored as plaintext
        in the Nix store. Use database.passwordFile instead.'';

    # Create database passwordFile default when password is configured.
    services.mailman3.database.passwordFile =
      (mkDefault (toString (pkgs.writeTextFile {
        name = "mailman3-database-password";
        text = cfg.database.password;
      })));

    systemd.services.mailman3 = {
      description = "GNU Mailing List Manager";
      after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
      wantedBy = [ "multi-user.target" ];

      preStart = let
        dbpass = (fileContents cfg.database.passwordFile);
        smtppass = (fileContents cfg.mta.smtp_passFile);
      in ''
        mkdir -p ${cfg.paths.etc_dir}
        cp ${configFile} ${cfg.paths.etc_dir}/mailman.cfg
        ${optionalString (useMysql || usePostgresql) ''
          sed -e "s/#dbpass#/${dbpass}/g" -e "s/#smtppass#/${smtppass}/g" -i ${cfg.paths.etc_dir}/mailman.cfg
        ''}
        chmod 640 ${cfg.paths.etc_dir}/mailman.cfg
      '';

      serviceConfig = {
        Type = "forking";
        User = cfg.user;
        WorkingDirectory = cfg.paths.var_dir;
        ExecStart = "${mailman3.core}/bin/mailman start -f";
        ExecReload = "${mailman3.core}/bin/mailman restart";
        ExecStop = "${mailman3.core}/bin/mailman stop";
      };

      environment = {
        USER = cfg.user;
        HOME = cfg.paths.var_dir;
        MAILMAN_CONFIG_FILE = "${cfg.paths.etc_dir}/mailman.cfg";
      };
    };
  };
}