{
  config,
  lib,
  pkgs,
  utils,
  ...
}: let
  inherit
    (lib)
    getExe
    mkDefault
    mkEnableOption
    mkIf
    mkMerge
    mkOption
    mkPackageOption
    optional
    types
    ;

  cfgApi = config.services.ente.api;
  cfgWeb = config.services.ente.web;

  webPackage = enteApp:
    cfgWeb.package.override {
      inherit enteApp;
      enteMainUrl = "https://${cfgWeb.domains.photos}";
      extraBuildEnv = {
        NEXT_PUBLIC_ENTE_ENDPOINT = "https://${cfgWeb.domains.api}";
        NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT = "https://${cfgWeb.domains.albums}";
        NEXT_TELEMETRY_DISABLED = "1";
      };
    };

  defaultUser = "ente";
  defaultGroup = "ente";
  dataDir = "/var/lib/ente";

  yamlFormat = pkgs.formats.yaml {};
in {
  options.services.ente = {
    web = {
      enable = mkEnableOption "Ente web frontend (Photos, Albums)";
      package = mkPackageOption pkgs "ente-web" {};

      domains = {
        api = mkOption {
          type = types.str;
          description = ''
            The domain under which the api is served. This will NOT serve the api itself,
            but is a required setting to host the frontends! This will automatically be set
            for you if you enable both the api server and web frontends.
          '';
        };

        accounts = mkOption {
          type = types.str;
          description = "The domain under which the accounts frontend will be served.";
        };

        cast = mkOption {
          type = types.str;
          description = "The domain under which the cast frontend will be served.";
        };

        albums = mkOption {
          type = types.str;
          description = "The domain under which the albums frontend will be served.";
        };

        photos = mkOption {
          type = types.str;
          description = "The domain under which the photos frontend will be served.";
        };

        auth = mkOption {
          type = types.str;
          description = "The domain under which the auth frontend will be served.";
        };
      };
    };

    api = {
      enable = mkEnableOption "Museum (API server for ente.io)";
      package = mkPackageOption pkgs "museum" {};
      nginx.enable = mkEnableOption "nginx proxy for the API server";

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

      group = mkOption {
        type = types.str;
        default = defaultGroup;
        description = "Group under which museum runs.";
      };

      domain = mkOption {
        type = types.str;
        description = "The domain under which the api will be served.";
      };

      enableLocalDB = mkEnableOption "the automatic creation of a local postgres database for museum.";

      settings = mkOption {
        description = ''
          Museum yaml configuration. Refer to upstream [local.yaml](https://github.com/ente-io/ente/blob/main/server/configurations/local.yaml) for more information.
          You can specify secret values in this configuration by setting `somevalue._secret = "/path/to/file"` instead of setting `somevalue` directly.
        '';
        default = {};
        type = types.submodule {
          freeformType = yamlFormat.type;
          options = {
            apps = {
              public-albums = mkOption {
                type = types.str;
                default = "https://albums.ente.io";
                description = ''
                  If you're running a self hosted instance and wish to serve public links,
                  set this to the URL where your albums web app is running.
                '';
              };

              cast = mkOption {
                type = types.str;
                default = "https://cast.ente.io";
                description = ''
                  Set this to the URL where your cast page is running.
                  This is for browser and chromecast casting support.
                '';
              };

              accounts = mkOption {
                type = types.str;
                default = "https://accounts.ente.io";
                description = ''
                  Set this to the URL where your accounts page is running.
                  This is primarily for passkey support.
                '';
              };
            };

            db = {
              host = mkOption {
                type = types.str;
                description = "The database host";
              };

              port = mkOption {
                type = types.port;
                default = 5432;
                description = "The database port";
              };

              name = mkOption {
                type = types.str;
                description = "The database name";
              };

              user = mkOption {
                type = types.str;
                description = "The database user";
              };
            };
          };
        };
      };
    };
  };

  config = mkMerge [
    (mkIf cfgApi.enable {
      services.postgresql = mkIf cfgApi.enableLocalDB {
        enable = true;
        ensureUsers = [
          {
            name = "ente";
            ensureDBOwnership = true;
          }
        ];
        ensureDatabases = ["ente"];
      };

      services.ente.web.domains.api = mkIf cfgWeb.enable cfgApi.domain;
      services.ente.api.settings = {
        log-file = mkDefault "";
        db = mkIf cfgApi.enableLocalDB {
          host = "/run/postgresql";
          port = 5432;
          name = "ente";
          user = "ente";
        };
        key = {
          encryption._secret = pkgs.writeText "encryption" "T0sn+zUVFOApdX4jJL4op6BtqqAfyQLH95fu8ASWfno=";
          hash._secret = pkgs.writeText "hash" "g/dBZBs1zi9SXQ0EKr4RCt1TGr7ZCKkgrpjyjrQEKovWPu5/ce8dYM6YvMIPL23MMZToVuuG+Z6SGxxTbxg5NQ==";
        };
        jwt.secret._secret = pkgs.writeText "jwt" "i2DecQmfGreG6q1vBj5tCokhlN41gcfS2cjOs9Po-u8=";
      };

      systemd.services.ente = {
        description = "Ente.io Museum API Server";
        after = ["network.target"] ++ optional cfgApi.enableLocalDB "postgresql.service";
        requires = optional cfgApi.enableLocalDB "postgresql.service";
        wantedBy = ["multi-user.target"];

        preStart = ''
          # Generate config including secret values. YAML is a superset of JSON, so we can use this here.
          ${utils.genJqSecretsReplacementSnippet cfgApi.settings "/run/ente/local.yaml"}

          # Setup paths
          mkdir -p ${dataDir}/configurations
          ln -sTf /run/ente/local.yaml ${dataDir}/configurations/local.yaml
        '';

        serviceConfig = {
          ExecStart = getExe cfgApi.package;
          Type = "simple";
          Restart = "on-failure";

          AmbientCapablities = [];
          CapabilityBoundingSet = [];
          LockPersonality = true;
          MemoryDenyWriteExecute = true;
          NoNewPrivileges = true;
          PrivateMounts = true;
          PrivateTmp = true;
          PrivateUsers = false;
          ProcSubset = "pid";
          ProtectClock = true;
          ProtectControlGroups = true;
          ProtectHome = true;
          ProtectHostname = true;
          ProtectKernelLogs = true;
          ProtectKernelModules = true;
          ProtectKernelTunables = true;
          ProtectProc = "invisible";
          ProtectSystem = "strict";
          RestrictAddressFamilies = [
            "AF_INET"
            "AF_INET6"
            "AF_NETLINK"
            "AF_UNIX"
          ];
          RestrictNamespaces = true;
          RestrictRealtime = true;
          RestrictSUIDSGID = true;
          SystemCallArchitectures = "native";
          SystemCallFilter = "@system-service";
          UMask = "077";

          BindReadOnlyPaths = [
            "${cfgApi.package}/share/museum/migrations:${dataDir}/migrations"
            "${cfgApi.package}/share/museum/mail-templates:${dataDir}/mail-templates"
            "${cfgApi.package}/share/museum/web-templates:${dataDir}/web-templates"
          ];

          User = cfgApi.user;
          Group = cfgApi.group;

          SyslogIdentifier = "ente";
          StateDirectory = "ente";
          WorkingDirectory = dataDir;
          RuntimeDirectory = "ente";
        };

        # Environment MUST be called local, otherwise we cannot log to stdout
        environment = {
          ENVIRONMENT = "local";
          GIN_MODE = "release";
        };
      };

      users = {
        users = mkIf (cfgApi.user == defaultUser) {
          ${defaultUser} = {
            description = "ente.io museum service user";
            inherit (cfgApi) group;
            isSystemUser = true;
            home = dataDir;
          };
        };
        groups = mkIf (cfgApi.group == defaultGroup) {${defaultGroup} = {};};
      };

      services.nginx = mkIf cfgApi.nginx.enable {
        enable = true;
        upstreams.museum = {
          servers."localhost:8080" = {};
          extraConfig = ''
            zone museum 64k;
            keepalive 20;
          '';
        };

        virtualHosts.${cfgApi.domain} = {
          forceSSL = mkDefault false;
          locations."/".proxyPass = "http://museum";
          extraConfig = ''
            client_max_body_size 4M;
          '';
        };
      };
    })
    (mkIf cfgWeb.enable {
      services.ente.api.settings = mkIf cfgApi.enable {
        apps = {
          accounts = "https://${cfgWeb.domains.accounts}";
          cast = "https://${cfgWeb.domains.cast}";
          public-albums = "https://${cfgWeb.domains.albums}";
        };

        webauthn = {
          rpid = cfgWeb.domains.accounts;
          rporigins = ["https://${cfgWeb.domains.accounts}"];
        };
      };

      services.nginx = let
        domainFor = app: cfgWeb.domains.${app};
      in {
        enable = true;
        virtualHosts.${domainFor "accounts"} = {
          listen = [
            {
              addr = "0.0.0.0";
              port = 3001;
            }
          ];
          locations."/" = {
            root = webPackage "accounts";
            tryFiles = "$uri $uri.html /index.html";
            extraConfig = ''
              add_header Access-Control-Allow-Origin 'https://${cfgWeb.domains.api}';
            '';
          };
        };
        virtualHosts.${domainFor "cast"} = {
          listen = [
            {
              addr = "0.0.0.0";
              port = 3004;
            }
          ];
          locations."/" = {
            root = webPackage "cast";
            tryFiles = "$uri $uri.html /index.html";
            extraConfig = ''
              add_header Access-Control-Allow-Origin 'https://${cfgWeb.domains.api}';
            '';
          };
        };
        virtualHosts.${domainFor "photos"} = {
          serverAliases = [
            (domainFor "albums") # the albums app is shared with the photos frontend
          ];
          listen = [
            {
              addr = "0.0.0.0";
              port = 3000;
            }
          ];
          locations."/" = {
            root = webPackage "photos";
            tryFiles = "$uri $uri.html /index.html";
            extraConfig = ''
              add_header Access-Control-Allow-Origin 'https://${cfgWeb.domains.api}';
            '';
          };
        };
        virtualHosts.${domainFor "auth"} = {
          listen = [
            {
              addr = "0.0.0.0";
              port = 3003;
            }
          ];
          locations."/" = {
            root = webPackage "auth";
            tryFiles = "$uri $uri.html /index.html";
            extraConfig = ''
              add_header Access-Control-Allow-Origin 'https://${cfgWeb.domains.api}';
            '';
          };
        };
      };
    })
  ];

  meta.maintainers = with lib.maintainers; [oddlama];
}
