# Copyright Tom Hubrecht, (2023)
#
# Tom Hubrecht <tom@hubrecht.ovh>
#
# This software is a computer program whose purpose is to configure
# machines and servers with NixOS.
#
# This software is governed by the CeCILL  license under French law and
# abiding by the rules of distribution of free software.  You can  use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and  rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using,  modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also
# therefore means  that it is reserved for developers  and  experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and,  more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.

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

let
  inherit (lib)
    mdDoc
    mkDefault
    mkEnableOption
    mkIf
    mkOption

    optional
    optionalString

    types
    ;

  cfg = config.services.demarches-simplifiees;

  settingsFormat = pkgs.formats.keyValue { };

  env = settingsFormat.generate "ds-fr-env" cfg.settings;

  ds-fr = pkgs.writeShellScriptBin "ds-fr" ''
    set -a
    cd ${cfg.package}

    ${optionalString (cfg.secretFile != null) "source ${cfg.secretFile}"}
    source ${env}

    BIN="$1"
    shift

    SUDO="exec"
    if [[ $USER != ${cfg.user} ]]; then
      SUDO='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env'
    fi

    $SUDO ${cfg.package}/bin/$BIN "$@"
  '';
in
{
  options.services.demarches-simplifiees = {
    enable = mkEnableOption "demarches-simplifiees.";

    package = mkOption {
      type = types.package;
      default = pkgs.callPackage ./package { inherit (cfg) initialDeploymentDate dataDir logDir; };
    };

    user = mkOption {
      type = types.str;
      default = "ds-fr";
      description = mdDoc "User account under which DS runs.";
    };

    group = mkOption {
      type = types.str;
      default = "ds-fr";
      description = mdDoc "Group account under which DS runs.";
    };

    dataDir = mkOption {
      type = types.str;
      default = "/var/lib/ds-fr";
    };

    logDir = mkOption {
      type = types.str;
      default = "/var/log/ds-fr";
    };

    secretFile = mkOption {
      type = types.nullOr types.path;
      default = null;
    };

    settings = mkOption { inherit (settingsFormat) type; };

    initialDeploymentDate = mkOption {
      type = types.nullOr types.str;
      default = null;
    };
  };

  config = mkIf cfg.enable {

    environment.systemPackages = [ ds-fr ];

    systemd.tmpfiles.rules = [
      "f '${cfg.logDir}/production.log' 0640 ${cfg.user} ${cfg.group} - -"
      "f '${cfg.dataDir}/.env' 0600 ${cfg.user} ${cfg.group} - -"
      "d '${cfg.dataDir}/tmp' 0700 ${cfg.user} ${cfg.group} 10d -"
      "d '${cfg.dataDir}/storage' 0700 ${cfg.user} ${cfg.group} - -"
    ];

    systemd.services = {
      ds-fr-setup = {
        description = "Demarches Simplifiees setup";

        wantedBy = [ "multi-user.target" ];
        path = [
          pkgs.bash
          ds-fr
        ];
        after = [ "postgresql.service" ];

        serviceConfig = {
          Type = "oneshot";
          User = cfg.user;
          Group = cfg.group;
          EnvironmentFile = [ env ] ++ (optional (cfg.secretFile != null) cfg.secretFile);
          StateDirectory = mkIf (cfg.dataDir == "/var/lib/ds-fr") "ds-fr";
          LogsDirectory = mkIf (cfg.logDir == "/var/log/ds-fr") "ds-fr";
        };

        script = ''
          [[ ! -f ${cfg.dataDir}/.initial-migration ]] \
            && ds-fr rails db:environment:set \
            && ds-fr rails db:schema:load \
            && ds-fr rails db:seed \
            && touch ${cfg.dataDir}/.initial-migration

          ds-fr rake db:migrate
          ds-fr rake after_party:run
        '';
      };

      ds-fr-work = {
        description = "Demarches Simplifiees work service";

        wantedBy = [
          "multi-user.target"
          "ds-fr.service"
        ];
        after = [
          "network.target"
          "ds-fr-setup.service"
        ];
        requires = [ "ds-fr-setup.service" ];

        serviceConfig = {
          ExecStart = "${ds-fr}/bin/ds-fr rails jobs:work";
          EnvironmentFile = [ env ] ++ (optional (cfg.secretFile != null) cfg.secretFile);
          User = cfg.user;
          Group = cfg.group;
          StateDirectory = mkIf (cfg.dataDir == "/var/lib/ds-fr") "ds-fr";
          LogsDirectory = mkIf (cfg.logDir == "/var/log/ds-fr") "ds-fr";
        };
      };

      ds-fr = {
        description = "Demarches Simplifiees web service";

        wantedBy = [ "multi-user.target" ];
        after = [
          "network.target"
          "ds-fr-setup.service"
        ];
        requires = [ "ds-fr-setup.service" ];
        path = [ pkgs.imagemagick ];

        serviceConfig = {
          ExecStart = "${ds-fr}/bin/ds-fr rails server";
          Environment = [ "RAILS_QUEUE_ADAPTER=delayed_job" ];
          EnvironmentFile = [ env ] ++ (optional (cfg.secretFile != null) cfg.secretFile);
          User = cfg.user;
          Group = cfg.group;
          StateDirectory = mkIf (cfg.dataDir == "/var/lib/ds-fr") "ds-fr";
          LogsDirectory = mkIf (cfg.logDir == "/var/log/ds-fr") "ds-fr";
        };
      };
    };

    services = {
      demarches-simplifiees.settings =
        (builtins.mapAttrs (_: mkDefault) {
          RAILS_ENV = "production";
          RAILS_ROOT = builtins.toString cfg.package;

          # Application host name
          #
          # Examples:
          # * For local development: localhost:3000
          # * For preproduction: staging.ds.example.org
          # * For production: ds.example.org
          APP_HOST = "localhost:3000";

          # Rails key for signing sensitive data
          # See https://guides.rubyonrails.org/security.html
          #
          # For production you MUST generate a new key, and keep it secret.
          # Secrets must be long and random. Use bin/rails secret to get new unique secrets.

          # Secret key for One-Time-Password codes, used for 2-factors authentication
          # OTP_SECRET_KEY = "";

          # Protect access to the instance with a static login/password (useful for staging environments)
          BASIC_AUTH_ENABLED = "disabled";
          BASIC_AUTH_USERNAME = "";
          BASIC_AUTH_PASSWORD = "";

          # ActiveStorage service to use for attached files.
          # Possible values:
          # - "local": store files on the local filesystem
          # - "amazon": store files remotely on an S3 storage service
          # - "openstack": store files remotely on an OpenStack storage service
          #
          # (See config/storage.yml for the configuration of each service.)
          ACTIVE_STORAGE_SERVICE = "local";

          # Configuration for the OpenStack storage service (if enabled)
          FOG_OPENSTACK_API_KEY = "";
          FOG_OPENSTACK_USERNAME = "";
          FOG_OPENSTACK_URL = "";
          FOG_OPENSTACK_REGION = "";
          DS_PROXY_URL = "";

          # SAML
          SAML_IDP_ENABLED = "disabled";

          # External service: authentication through France Connect
          FC_PARTICULIER_ID = "";
          FC_PARTICULIER_SECRET = "";
          FC_PARTICULIER_BASE_URL = "";

          # External service: authentication through Agent Connect
          AGENT_CONNECT_ID = "";
          AGENT_CONNECT_SECRET = "";
          AGENT_CONNECT_BASE_URL = "";
          AGENT_CONNECT_JWKS = "";
          AGENT_CONNECT_REDIRECT = "";

          # External service: integration with HelpScout (optional)
          HELPSCOUT_MAILBOX_ID = "";
          HELPSCOUT_CLIENT_ID = "";
          HELPSCOUT_CLIENT_SECRET = "";
          HELPSCOUT_WEBHOOK_SECRET = "";

          # External service: external supervision
          SENTRY_ENABLED = "disabled";
          SENTRY_CURRENT_ENV = "development";
          SENTRY_DSN_RAILS = "";
          SENTRY_DSN_JS = "";

          # External service: Matomo web analytics
          MATOMO_ENABLED = "disabled";
          MATOMO_COOKIE_DOMAIN = "*.www.demarches-simplifiees.fr";
          MATOMO_DOMAIN = "*.www.demarches-simplifiees.fr";
          MATOMO_ID = "";
          MATOMO_HOST = "matomo.example.org";

          # Default SMTP Provider: Mailjet
          MAILJET_API_KEY = "";
          MAILJET_SECRET_KEY = "";

          # Alternate SMTP Provider: SendInBlue/DoList
          SENDINBLUE_CLIENT_KEY = "";
          SENDINBLUE_SMTP_KEY = "";
          SENDINBLUE_USER_NAME = "";
          # SENDINBLUE_LOGIN_URL="https://app.sendinblue.com/account/saml/login/truc"

          # Alternate SMTP Provider: Mailtrap (mail catcher for staging environments)
          # When enabled, all emails will be sent using this provider
          MAILTRAP_ENABLED = "disabled";
          MAILTRAP_USERNAME = "";
          MAILTRAP_PASSWORD = "";

          # Alternative SMTP Provider: Mailcatcher (Catches mail and serves it through a dream.)
          # When enabled, all emails will be sent using this provider
          MAILCATCHER_ENABLED = "disabled";
          MAILCATCHER_HOST = "";
          MAILCATCHER_PORT = "";

          # External service: live chat for admins (specific to démarches-simplifiées.fr)
          CRISP_ENABLED = "disabled";
          CRISP_CLIENT_KEY = "";

          # API Entreprise credentials
          # https://api.gouv.fr/api/api-entreprise.html
          API_ENTREPRISE_KEY = "";

          # External service: CRM for following admin accounts pipeline (specific to démarches-simplifiées.fr)
          PIPEDRIVE_KEY = "";

          # Networks bypassing the email login token that verifies new devices, and rack-attack throttling
          TRUSTED_NETWORKS = "";

          # External service: mesuring performance of the Rails app (specific to démarches-simplifiées.fr)
          SKYLIGHT_AUTHENTICATION_KEY = "";
          #   "sXaot-fKhBlkI8qaSirQyuZbrpv5sVFoOturQ0pFEh0";

          # Enable or disable Lograge logs
          LOGRAGE_ENABLED = "disabled";

          # Logs source for Lograge
          #
          # Examples:
          # * For local development: tps_local
          # * For preproduction: tps_staging
          # * For production: tps_prod
          LOGRAGE_SOURCE = "tps_prod";

          # External service: timestamping a daily archive of dossiers status changes
          UNIVERSIGN_API_URL = "https://ws.universign.eu/tsa/post/";
          UNIVERSIGN_USERPWD = "";

          # External service: API Geo / Adresse
          API_ADRESSE_URL = "https://api-adresse.data.gouv.fr";
          API_GEO_URL = "https://geo.api.gouv.fr";

          # External service: API Education
          API_EDUCATION_URL = "https://data.education.gouv.fr/api/records/1.0";

          # Encryption key for sensitive columns in the database
          ENCRYPTION_SERVICE_SALT = "";

          # ActiveRecord encryption keys. Generate them with bin/rails db:encryption:init (you can omit deterministic_key)
          AR_ENCRYPTION_PRIMARY_KEY = "";
          AR_ENCRYPTION_KEY_DERIVATION_SALT = "";

          # Salt for invisible_captcha session data.
          # Must be the same value for all app instances behind a load-balancer.
          INVISIBLE_CAPTCHA_SECRET = "kikooloool";

          # Clamav antivirus usage
          CLAMAV_ENABLED = "disabled";

          # Siret number used for API Entreprise, by default we use SIRET from dinum
          API_ENTREPRISE_DEFAULT_SIRET = "put_your_own_siret";
        })
        // {
          # Database credentials
          DB_DATABASE = "ds-fr";
          DB_USERNAME = cfg.user;
          DB_PASSWORD = "";
          DB_HOST = "/run/postgresql";
          DB_POOL = "";

          # Log on stdout
          RAILS_LOG_TO_STDOUT = true;
        };

      postgresql = {
        enable = true;

        ensureDatabases = [ "ds-fr" ];

        ensureUsers = optional (cfg.user == "ds-fr") {
          name = "ds-fr";
          ensureDBOwnership = true;
        };

        extraPlugins = with config.services.postgresql.package.pkgs; [ postgis ];
      };

      nginx = {
        enable = true;

        virtualHosts.${cfg.settings.APP_HOST} = {
          enableACME = true;
          forceSSL = true;
          root = "${cfg.package}/public/";

          locations."/".tryFiles = "$uri @proxy";
          locations."@proxy" = {
            proxyPass = "http://127.0.0.1:3000";
          };
        };
      };
    };

    users.users = mkIf (cfg.user == "ds-fr") {
      ds-fr = {
        inherit (cfg) group;

        isSystemUser = true;
        home = cfg.package;
      };
    };

    users.groups.${cfg.group} = { };
  };
}