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

let
  cfg = config.my.services.bsky-pds;

  marshalValue = key: value: if builtins.isString value then value
    else if builtins.isInt value then toString value
    else if builtins.isBool value then (if value then "true" else "false")
    else throw "unknown value type for key ${key}";
  cfgFile = pkgs.writeTextFile {
    name = "bsky-pds.env";
    text = lib.concatStringsSep "\n" (lib.mapAttrsToList (attrName: attrVal: lib.optionalString (attrVal != null) ''
      ${lib.toUpper attrName}=${marshalValue attrName attrVal}
    '') cfg.settings);
  };

  inSecretsDir = v: builtins.isString v && (builtins.match "/var/lib/bsky-pds/secrets/.*" (toString v)) != null;

  testAndGenerate = how: path: ''
    test -f "${path}" || {
      ${how} >> "${path}"
    }
  '';
  testAndGenerateHex = testAndGenerate "${pkgs.openssl}/bin/openssl rand --hex 16";
  testAndGenerateK256 = testAndGenerate "${pkgs.openssl}/bin/openssl ecparam --name secp256k1 --genkey --noout --outform DER";
in
{
  options.my.services.bsky-pds = {
    enable = lib.mkEnableOption "Bluesky Personal Data Server";

    package = lib.mkOption {
      type = lib.types.package;
      default = depot.nix.pkgs.bsky-pds;
      description = ''
        Package containing the PDS code.
      '';
    };

    configureCaddy = (lib.mkEnableOption "configure Caddy to serve the PDS") // {
      default = true;
    };

    settings = lib.mkOption {
      type = with lib.types; submodule {
        freeformType = attrsOf (nullOr (oneOf [
          str
          bool
          int
        ]));

        options.log_enabled = lib.mkOption {
          type = lib.types.bool;
          default = true;
          description = ''
            Whether or not to output any log messages.
          '';
        };
        options.port = lib.mkOption {
          type = lib.types.port;
          default = 3000;
          description = ''
            Port number that the PDS will listen on.
          '';
        };
        options.pds_hostname = lib.mkOption {
          type = lib.types.str;
          description = ''
            Hostname under which the PDS will run.
          '';
        };
        options.pds_admin_email = lib.mkOption {
          type = lib.types.str;
          description = ''
            Email address of the PDS operator.
          '';
        };
        options.pds_data_directory = lib.mkOption {
          type = lib.types.path;
          default = "/var/lib/bsky-pds/data";
          description = ''
            Path to data directory.
          '';
        };
        options.pds_blobstore_disk_location = lib.mkOption {
          type = lib.types.nullOr lib.types.path;
          default = "/var/lib/bsky-pds/blobs";
          description = ''
            Path to location for blobstore storage, if using on-disk storage.
          '';
        };
        options.pds_blob_upload_limit = lib.mkOption {
          type = lib.types.int;
          default = 52428800;
          description = ''
            Maximum allowable blob size for upload.
          '';
        };
        options.pds_did_plc_url = lib.mkOption {
          type = lib.types.str;
          default = "https://plc.directory";
          description = ''
            URL of the PLC directory.
          '';
        };

        options.pds_bsky_app_view_url = lib.mkOption {
          type = lib.types.str;
          default = "https://api.bsky.app";
          description = ''
            URL of the Bluesky AppView.
          '';
        };
        options.pds_bsky_app_view_did = lib.mkOption {
          type = lib.types.str;
          default = "did:web:api.bsky.app";
          description = ''
            DID of the Bluesky AppView.
          '';
        };
        options.pds_report_service_url = lib.mkOption {
          type = lib.types.str;
          default = "https://mod.bsky.app";
          description = ''
            URL of the Bluesky moderation service.
          '';
        };
        options.pds_report_service_did = lib.mkOption {
          type = lib.types.str;
          default = "did:plc:ar7c4by46qjdydhdevvrndac";
          description = ''
            DID of the Bluesky moderation service.
          '';
        };
        options.crawlers = lib.mkOption {
          type = lib.types.commas;
          default = "https://bsky.network";
          description = ''
            URLs of hosts to notify of new data.
          '';
        };
      };
      default = {};
      description = ''
        Configuration for Bluesky PDS.
      '';
    };

    generateSecrets = lib.mkEnableOption "automatically generate required PDS secrets";

    secrets = lib.mkOption {
      type = with lib.types; submodule {
        freeformType = attrsOf (nullOr path);

        options.pds_jwt_secret = lib.mkOption {
          type = nullOr path;
          default = null;
          description = ''
            Path to a file containing a 16 character hex secret used for JWT secrets.
          '';
        };
        options.pds_admin_password = lib.mkOption {
          type = nullOr path;
          default = null;
          description = ''
            Path to a file containing the PDS admin password.
          '';
        };
        options.pds_plc_rotation_key_k256_private_key = lib.mkOption {
          type = nullOr path;
          default = null;
          description = ''
            Path to a file containing a secp256k1 private key in DER format.
          '';
        };
      };
      default = {};
      description = ''
        Path to files containing secrets for Bluesky PDS.
      '';
    };
  };

  config = lib.mkMerge [(lib.mkIf cfg.generateSecrets {
    my.services.bsky-pds.secrets = {
      pds_jwt_secret = lib.mkDefault "/var/lib/bsky-pds/secrets/pds_jwt_secret";
      pds_admin_password = lib.mkDefault "/var/lib/bsky-pds/secrets/pds_admin_password";
      pds_plc_rotation_key_k256_private_key = lib.mkDefault "/var/lib/bsky-pds/secrets/pds_plc_rotation_key_k256_private_key";
    };
  }) (lib.mkIf cfg.enable {
    systemd.services.bsky-pds = {
      wantedBy = [ "multi-user.target" ];
      unitConfig = {
        Description = "Bluesky PDS Service";
        Documentation = "https://github.com/bluesky-social/pds";
      };
      script = ''
        mkdir -p /var/lib/bsky-pds "$PDS_DATA_DIRECTORY"
        if [[ ! -z "$PDS_BLOBSTORE_DISK_LOCATION" ]]; then
          mkdir -p "$PDS_BLOBSTORE_DISK_LOCATION"
        fi

        old_umask=$(umask)
        umask 077
        mkdir -p /var/lib/bsky-pds/secrets
        ${lib.optionalString (inSecretsDir cfg.secrets.pds_jwt_secret) (testAndGenerateHex cfg.secrets.pds_jwt_secret)}
        ${lib.optionalString (inSecretsDir cfg.secrets.pds_admin_password) (testAndGenerateHex cfg.secrets.pds_admin_password)}
        ${lib.optionalString (inSecretsDir cfg.secrets.pds_plc_rotation_key_k256_private_key) (testAndGenerateK256 cfg.secrets.pds_plc_rotation_key_k256_private_key)}
        umask "$old_umask"

        ${lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: lib.optionalString (v != null) ''
          export ${lib.toUpper k}="$(cat "${v}")"
        '') cfg.secrets)}

        ${lib.optionalString (cfg.secrets.pds_plc_rotation_key_k256_private_key != null) ''
          if [[ -z "$PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX ]]; then
            export PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX="$(cat "${cfg.secrets.pds_plc_rotation_key_k256_private_key}" | ${pkgs.coreutils}/bin/tail --bytes=+8 | ${pkgs.coreutils}/bin/head --bytes=32 | ${pkgs.xxd}/bin/xxd --plain --cols 32)"
          fi
        ''}

        exec ${cfg.package}/bin/bsky-pds
      '';
      serviceConfig = {
        User = "bsky-pds";
        DynamicUser = true;
        StateDirectory = "bsky-pds";
        EnvironmentFile = cfgFile;
      };
    };
  }) (lib.mkIf (cfg.enable && cfg.configureCaddy) {
    services.caddy = {
      enable = lib.mkDefault true;
      globalConfig = ''
        email ${cfg.settings.pds_admin_email}
        on_demand_tls {
          ask http://localhost:${toString cfg.settings.port}/tls-check
        }
      '';
      virtualHosts."${cfg.settings.pds_hostname}" = {
        serverAliases = [ "*.${cfg.settings.pds_hostname}" ];
        extraConfig = ''
          tls {
            on_demand
          }
          reverse_proxy http://localhost:${toString cfg.settings.port}
        '';
      };
    };
  })];
}