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

with lib;

let
  cfg = config.services.coder;
  name = "coder";
in {
  options = {
    services.coder = {
      enable = mkEnableOption (lib.mdDoc "Coder service");

      user = mkOption {
        type = types.str;
        default = "coder";
        description = lib.mdDoc ''
          User under which the coder service runs.

          ::: {.note}
          If left as the default value this user will automatically be created
          on system activation, otherwise it needs to be configured manually.
          :::
        '';
      };

      group = mkOption {
        type = types.str;
        default = "coder";
        description = lib.mdDoc ''
          Group under which the coder service runs.

          ::: {.note}
          If left as the default value this group will automatically be created
          on system activation, otherwise it needs to be configured manually.
          :::
        '';
      };

      package = mkOption {
        type = types.package;
        default = pkgs.coder;
        description = lib.mdDoc ''
          Package to use for the service.
        '';
        defaultText = literalExpression "pkgs.coder";
      };

      homeDir = mkOption {
        type = types.str;
        description = lib.mdDoc ''
          Home directory for coder user.
        '';
        default = "/var/lib/coder";
      };

      listenAddress = mkOption {
        type = types.str;
        description = lib.mdDoc ''
          Listen address.
        '';
        default = "127.0.0.1:3000";
      };

      accessUrl = mkOption {
        type = types.nullOr types.str;
        description = lib.mdDoc ''
          Access URL should be a external IP address or domain with DNS records pointing to Coder.
        '';
        default = null;
        example = "https://coder.example.com";
      };

      wildcardAccessUrl = mkOption {
        type = types.nullOr types.str;
        description = lib.mdDoc ''
          If you are providing TLS certificates directly to the Coder server, you must use a single certificate for the root and wildcard domains.
        '';
        default = null;
        example = "*.coder.example.com";
      };

      database = {
        createLocally = mkOption {
          type = types.bool;
          default = true;
          description = lib.mdDoc ''
            Create the database and database user locally.
          '';
        };

        host = mkOption {
          type = types.str;
          default = "/run/postgresql";
          description = lib.mdDoc ''
            Hostname hosting the database.
          '';
        };

        database = mkOption {
          type = types.str;
          default = "coder";
          description = lib.mdDoc ''
            Name of database.
          '';
        };

        username = mkOption {
          type = types.str;
          default = "coder";
          description = lib.mdDoc ''
            Username for accessing the database.
          '';
        };

        password = mkOption {
          type = types.nullOr types.str;
          default = null;
          description = lib.mdDoc ''
            Password for accessing the database.
          '';
        };

        sslmode = mkOption {
          type = types.nullOr types.str;
          default = "disable";
          description = lib.mdDoc ''
            Password for accessing the database.
          '';
        };
      };

      tlsCert = mkOption {
        type = types.nullOr types.path;
        description = lib.mdDoc ''
          The path to the TLS certificate.
        '';
        default = null;
      };

      tlsKey = mkOption {
        type = types.nullOr types.path;
        description = lib.mdDoc ''
          The path to the TLS key.
        '';
        default = null;
      };
    };
  };

  config = mkIf cfg.enable {
    assertions = [
      { assertion = cfg.database.createLocally -> cfg.database.username == name;
        message = "services.coder.database.username must be set to ${user} if services.coder.database.createLocally is set true";
      }
    ];

    systemd.services.coder = {
      description = "Coder - Self-hosted developer workspaces on your infra";
      after = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];

      environment = {
        CODER_ACCESS_URL = cfg.accessUrl;
        CODER_WILDCARD_ACCESS_URL = cfg.wildcardAccessUrl;
        CODER_PG_CONNECTION_URL = "user=${cfg.database.username} ${optionalString (cfg.database.password != null) "password=${cfg.database.password}"} database=${cfg.database.database} host=${cfg.database.host} ${optionalString (cfg.database.sslmode != null) "sslmode=${cfg.database.sslmode}"}";
        CODER_ADDRESS = cfg.listenAddress;
        CODER_TLS_ENABLE = optionalString (cfg.tlsCert != null) "1";
        CODER_TLS_CERT_FILE = cfg.tlsCert;
        CODER_TLS_KEY_FILE = cfg.tlsKey;
      };

      serviceConfig = {
        ProtectSystem = "full";
        PrivateTmp = "yes";
        PrivateDevices = "yes";
        SecureBits = "keep-caps";
        AmbientCapabilities = "CAP_IPC_LOCK CAP_NET_BIND_SERVICE";
        CacheDirectory = "coder";
        CapabilityBoundingSet = "CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE";
        KillSignal = "SIGINT";
        KillMode = "mixed";
        NoNewPrivileges = "yes";
        Restart = "on-failure";
        ExecStart = "${cfg.package}/bin/coder server";
        User = cfg.user;
        Group = cfg.group;
      };
    };

    services.postgresql = lib.mkIf cfg.database.createLocally {
      enable = true;
      ensureDatabases = [
        cfg.database.database
      ];
      ensureUsers = [{
        name = cfg.database.username;
        ensurePermissions = {
          "DATABASE \"${cfg.database.database}\"" = "ALL PRIVILEGES";
        };
        }
      ];
    };

    users.groups = optionalAttrs (cfg.group == name) {
      "${cfg.group}" = {};
    };
    users.users = optionalAttrs (cfg.user == name) {
      ${name} = {
        description = "Coder service user";
        group = cfg.group;
        home = cfg.homeDir;
        createHome = true;
        isSystemUser = true;
      };
    };
  };
}