{
  config,
  pkgs,
  lib,
  ...
}:
let
  cfg = config.services.think-backend-greaterchiangmai;
  think-backend-gtcm = pkgs.think-backend-gtcm.override { dataDir = cfg.dataDir; };
  file-uploader = pkgs.gtcm-file-uploader.override { dataDir = cfg.dataDir; };

  nginxNodeProxyConfig = ''
    proxy_pass http://127.0.0.1:3000;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_set_header x-webobjects-server-protocol HTTP/1.0;
    proxy_set_header x-webobjects-remote-host 127.0.0.1;
    proxy_set_header x-webobjects-server-port $server_port;
    proxy_set_header x-webobjects-server-name $server_name;
    proxy_set_header x-webobjects-server-url $scheme://$host;
    proxy_connect_timeout 90;
    proxy_send_timeout 90;
    proxy_read_timeout 90;
    proxy_buffer_size 64k;
    proxy_buffers 8 64k;
    proxy_busy_buffers_size 64k;
    proxy_temp_file_write_size 64k;
    client_max_body_size 50m;
    client_body_buffer_size 128k;
  '';

  defaultUser = "gtcm";
  defaultGroup = "gtcm";

  php = pkgs.php83;

  artisan-be = pkgs.writeScriptBin "gtcm-be" ''
    #! ${pkgs.runtimeShell}
    cd ${think-backend-gtcm}
    sudo() {
      if [[ "$USER" != ${cfg.user} ]]; then
        exec /run/wrappers/bin/sudo -u ${cfg.user} "$@"
      else
        exec "$@"
      fi
    }
    sudo ${lib.getExe php} artisan "$@"
  '';

in
{
  options.services.think-backend-greaterchiangmai = {
    enable = lib.mkEnableOption "To enable think.greaterchiangmai.com";

    dataDir = lib.mkOption {
      type = lib.types.path;
      default = "/var/lib/think-backend.greaterchiangmai.com";
      description = "A place where to store states";
    };

    user = lib.mkOption {
      type = lib.types.str;
      default = defaultUser;
      description = "User account under which this runs.";
    };

    group = lib.mkOption {
      type = lib.types.str;
      default = defaultGroup;
      defaultText = "${defaultGroup}";
      description = "Group under which the website runs.";
    };

    package = lib.mkPackageOption pkgs "think-backend-gtcm" { };

    domain = lib.mkOption {
      type = lib.types.str;
      default = "think-backend.greaterchiangmai.com";
      example = "forum.example.com";
      description = "Domain to serve on.";
    };

    settings = lib.mkOption {
      type =
        with lib.types;
        attrsOf (
          nullOr (
            either
              (oneOf [
                bool
                int
                port
                path
                str
              ])
              (submodule {
                options = {
                  _secret = lib.mkOption {
                    type = nullOr str;
                    description = ''
                      The path to a file containing the value the
                      option should be set to in the final
                      configuration file.
                    '';
                  };
                };
              })
          )
        );
      default = { };
      description = ''
        Options for settings environment variables
      '';
      example = lib.literalExpression ''
        {
          APP_NAME = "Laravel";
          APP_ENV = "local";
          APP_KEY = "key";
          APP_DEBUG = true;
          APP_URL = "http://localhost";

          LOG_CHANNEL = "stack";
          LOG_DEPRECATIONS_CHANNEL = "null";
          LOG_LEVEL = "debug";

          DB_CONNECTION = "mysql";
          DB_HOST = "127.0.0.1";
          DB_PORT = "3306";
          DB_DATABASE = "laravel";
          DB_USERNAME = "root";
          DB_PASSWORD = "";
        }
      '';
    };

  };
  config = lib.mkIf cfg.enable {
    users.users.${cfg.user} = lib.mkForce {
      isSystemUser = true;
      home = cfg.dataDir;
      createHome = true;
      homeMode = "755";
      group = cfg.group;
    };
    users.groups.${cfg.group} = { };

    services.phpfpm.pools.think-backend-gtcm = {
      inherit (cfg) user group;
      phpPackage = php;
      settings = {
        "listen.owner" = config.services.nginx.user;
        "listen.group" = config.services.nginx.group;
        "listen.mode" = "0600";
        "pm" = lib.mkDefault "dynamic";
        "pm.max_children" = lib.mkDefault 10;
        "pm.max_requests" = lib.mkDefault 500;
        "pm.start_servers" = lib.mkDefault 2;
        "pm.min_spare_servers" = lib.mkDefault 1;
        "pm.max_spare_servers" = lib.mkDefault 3;
      };
      phpOptions = ''
        error_log = syslog
        log_errors = on
      '';
    };

    systemd.services.gtcm-file-uploader = {
      description = "File upload service for think-backend.greaterchiangmai.com";
      requiredBy = [ "phpfpm-think-backend-gtcm.service" ];
      before = [ "phpfpm-think-backend-gtcm.service" ];
      serviceConfig = {
        User = cfg.user;
        WorkingDirectory = "${file-uploader}";
        ExecStart = "${lib.getExe pkgs.nodejs_20} ${file-uploader}/src/be/index.js";
        Restart = "on-failure";
      };
      path = [ pkgs.nodejs_20 ];
    };

    environment.systemPackages = [
      artisan-be
    ];

    services.think-backend-greaterchiangmai.settings = {
      APP_SERVICES_CACHE = lib.mkDefault "${cfg.dataDir}/cache/services.php";
      APP_PACKAGES_CACHE = lib.mkDefault "${cfg.dataDir}/cache/packages.php";
      APP_CONFIG_CACHE = lib.mkDefault "${cfg.dataDir}/cache/config.php";
      APP_ROUTES_CACHE = lib.mkDefault "${cfg.dataDir}/cache/routes-v7.php";
      APP_EVENTS_CACHE = lib.mkDefault "${cfg.dataDir}/cache/events.php";
    };

    systemd.services.think-backend-gtcm-setup = {
      description = "think-backend.greaterchiangmai installation";
      requiredBy = [ "phpfpm-think-backend-gtcm.service" ];
      before = [ "phpfpm-think-backend-gtcm.service" ];
      requires = [ "mysql.service" ];
      after = [ "mysql.service" ];
      serviceConfig = {
        type = "oneshot";
        RemainAfterExit = true;
        User = cfg.user;
        UMask = 77;
        WorkingDirectory = "${think-backend-gtcm}";
        RuntimeDirectory = "think-backend-gtcm/cache";
        RuntimeDirectoryMode = 700;
      };
      path = [ pkgs.replace-secret ];
      script =
        let
          isSecret = v: lib.isAttrs v && v ? _secret && lib.isString v._secret;
          gtcmEnvVars = lib.generators.toKeyValue {
            mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
              mkValueString =
                v:
                with builtins;
                if isInt v then
                  toString v
                else if isString v then
                  v
                else if true == v then
                  "true"
                else if false == v then
                  "false"
                else if isSecret v then
                  hashString "sha256" v._secret
                else
                  throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty { }) v}";
            };
          };
          secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.settings);
          mkSecretReplacement = file: ''
            replace-secret ${
              lib.escapeShellArgs [
                (builtins.hashString "sha256" file)
                file
                "${cfg.dataDir}/.env"
              ]
            }
          '';
          secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
          filteredConfig = lib.converge (lib.filterAttrsRecursive (
            _: v:
            !lib.elem v [
              { }
              null
            ]
          )) cfg.settings;
          gtcmEnv = pkgs.writeText "gtcm-be.env" (gtcmEnvVars filteredConfig);
        in
        ''
          # error handling
          set -euo pipefail

          # create .env file
          install -T -m 0600 -o ${cfg.user} ${gtcmEnv} "${cfg.dataDir}/.env"
          ${secretReplacements}
          if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
            sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
          fi

          # migrate & seed db
          ${lib.getExe php} artisan key:generate --force
          ${lib.getExe php} artisan migrate --force
          ${lib.getExe php} artisan storage:link
          ${lib.getExe php} artisan config:cache
        '';
    };

    systemd.tmpfiles.rules = [
      "d ${cfg.dataDir}                            0710 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/cache                      0700 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/public                     0755 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/public/uploads             0755 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/storage                    0700 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/storage/app                0700 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/storage/fonts              0700 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/storage/framework          0700 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/storage/framework/cache    0700 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/storage/framework/sessions 0700 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/storage/framework/views    0700 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/storage/logs               0700 ${cfg.user} ${cfg.group} - -"
      "d ${cfg.dataDir}/storage/uploads            0700 ${cfg.user} ${cfg.group} - -"

      "d ${cfg.dataDir}/gtcm-file-uploader/uploads      0700 ${cfg.user} ${cfg.group} - -"
    ];

    networking.firewall.allowedTCPPorts = [
      80
      443
    ];

    services.nginx = {
      enable = true;
      recommendedTlsSettings = true;
      recommendedOptimisation = true;
      recommendedGzipSettings = true;
      recommendedBrotliSettings = true;
      recommendedProxySettings = true;
      virtualHosts."${cfg.domain}" = {
        root = "${think-backend-gtcm}/public";
        locations = {
          "/" = {
            index = "index.php";
            tryFiles = "$uri $uri/ /index.php?$query_string";
          };
          "/uploads" = {
            root = "${cfg.dataDir}/public/uploads";
            index = "index.php index.html";
            tryFiles = "$uri $uri/ /index.php";
          };
          "/storage" = {
            root = "${cfg.dataDir}/public/storage";
            index = "index.php index.html";
            tryFiles = "$uri $uri/ /index.php";
          };
          "~ \\.php$".extraConfig = ''
            fastcgi_pass unix:${config.services.phpfpm.pools."think-backend-gtcm".socket};
          '';
          "~ \\.(js|css|gif|png|ico|jpg|jpeg)$" = {
            extraConfig = "expires 365d;";
          };
          "^~ /initiate-multipart-upload".extraConfig = nginxNodeProxyConfig;
          "^~ /get-presigned-url".extraConfig = nginxNodeProxyConfig;
          "^~ /complete-multipart-upload".extraConfig = nginxNodeProxyConfig;
          "^~ /generate-presigned-url".extraConfig = nginxNodeProxyConfig;
          "^~ /enable-bucket-cors".extraConfig = nginxNodeProxyConfig;
          "^~ /upload".extraConfig = nginxNodeProxyConfig;
        };
      };
    };

    services.mysql = {
      enable = true;
      package = lib.mkForce pkgs.mariadb;
      ensureDatabases = [ cfg.settings.DB_DATABASE ];
      ensureUsers = [
        {
          name = cfg.settings.DB_USERNAME;
          ensurePermissions = {
            "${cfg.settings.DB_DATABASE}.*" = "ALL PRIVILEGES";
          };
        }
      ];
    };
  };
}
