{
  config,
  pkgs,
  lib,
  ...
}:
let
  domain = "login.kaareskovgaard.net";
  bootstrapping = config.khscodes."security.kaareskovgaard.net".bootstrap.enable;
  openbaoAppBasicSecretFile = "/var/lib/vault-agent/kanidm/openbao_basic_secret";
  openbaoCliAppBasicSecretFile = "/var/lib/vault-agent/kanidm/openbao_cli_basic_secret";
  monitoringAppBasicSecretFile = "/var/lib/vault-agent/kanidm/monitoring_basic_secret";
  forgejoAppBasicSecretFile = "/var/lib/vault-agent/kanidm/monitoring_basic_secret";
  openbaoDomain = config.khscodes.infrastructure.openbao.domain;
  openbaoAllowedRedirectUrls = [
    "https://${openbaoDomain}/ui/vault/auth/kanidm/oidc/callback"
    "https://${openbaoDomain}/kanidm/callback"
  ];
  kanidm-reset-password = pkgs.writeShellApplication {
    name = "kanidm-reset-password";
    runtimeInputs = [
      config.services.kanidm.package
      pkgs.jq
      pkgs.gnugrep
      pkgs.expect
    ];
    text = ''
      username="''${1:-}"
      if [[ "$username" == "" ]]; then
        >&2 echo "Usage kanidm-reset-password <username>"
        exit 1
      fi
      echo "Resetting password for idm_admin, please provide sudo password"
      password="$(sudo -u kanidm kanidmd recover-account -c /etc/kanidm/server.toml -o json idm_admin 2> /dev/null | grep '^{' | jq --raw-output '.password')"
      expect <<EOD
      spawn kanidm login --url https://login.kaareskovgaard.net --name idm_admin
      expect "Enter password"
      send "$password\n"
      expect eof
      EOD
      kanidm person credential create-reset-token --url https://login.kaareskovgaard.net "$username"
      trap "kanidm logout --url https://login.kaareskovgaard.net" EXIT
    '';

  };
in
{
  services.kanidm = {
    enableServer = true;
    # Verify upgrade will be successful by running `kanidmd domain upgrade-check` before bumping package version.
    package = pkgs.kanidmWithSecretProvisioning_1_6;
    serverSettings = {
      inherit domain;
      tls_chain = "${config.security.acme.certs.${domain}.directory}/fullchain.pem";
      tls_key = "${config.security.acme.certs.${domain}.directory}/key.pem";
      origin = "https://${domain}";
      trust_x_forward_for = true;
    };
    provision = {
      enable = true;
      idmAdminPasswordFile = null;
      adminPasswordFile = null;
      acceptInvalidCerts = true;
      persons.khs = {
        present = true;
        mailAddresses = [ "kaare@kaareskovgaard.net" ];
        legalName = "Kaare Skovgaard";
        displayName = "khs";
      };
      groups.openbao_admin = {
        present = true;
        members = [ "khs" ];
      };
      groups.forgejo_user = {
        present = true;
        members = [ "khs" ];
      };
      groups.forgejo_comitter = {
        present = true;
        members = [ "khs" ];
      };
      groups.forgejo_admin = {
        present = true;
        members = [ "khs" ];
      };
      # We cannot add oauth2 apps before the secrets for them are generated.
      systems.oauth2 = lib.mkIf (!bootstrapping) {
        openbao = {
          present = true;
          public = false;
          preferShortUsername = true;
          basicSecretFile = lib.mkIf (!bootstrapping) openbaoAppBasicSecretFile;
          originUrl = openbaoAllowedRedirectUrls;
          originLanding = "https://${openbaoDomain}";
          displayName = "OpenBAO";
          scopeMaps = {
            "openbao_admin" = [
              "profile"
              "email"
              "openid"
            ];
          };
          claimMaps.groups = {
            joinType = "array";
            valuesByGroup = {
              "openbao_admin" = [
                "openbao_writer"
                "openbao_cli_writer"
              ];
            };
          };
        };
        openbao-cli = {
          present = true;
          public = false;
          preferShortUsername = true;
          basicSecretFile = lib.mkIf (!bootstrapping) openbaoCliAppBasicSecretFile;
          originUrl = [ "http://localhost:8250/oidc/callback" ];
          originLanding = "http://localhost:8250";
          displayName = "OpenBAO CLI";
          scopeMaps = {
            "openbao_admin" = [
              "profile"
              "email"
              "openid"
            ];
          };
          claimMaps.groups = {
            joinType = "array";
            valuesByGroup = {
              "openbao_admin" = [
                "openbao_writer"
              ];
            };
          };
        };
        monitoring = {
          present = true;
          public = false;
          preferShortUsername = true;
          basicSecretFile = lib.mkIf (!bootstrapping) monitoringAppBasicSecretFile;
          originUrl = [ "https://monitoring.kaareskovgaard.net/login/generic_oauth" ];
          originLanding = "http://monitoring.kaareskovgaard.net";
          displayName = "Monitoring";
          scopeMaps = {
            "openbao_admin" = [
              "profile"
              "email"
              "openid"
            ];
          };
        };
        forgejo = {
          present = true;
          public = false;
          preferShortUsername = true;
          basicSecretFile = lib.mkIf (!bootstrapping) forgejoAppBasicSecretFile;
          originUrl = [ "https://kas.codes/user/oauth2/Kanidm/callback" ];
          originLanding = "http://kas.codes";
          displayName = "KAS: Codes";
          scopeMaps = {
            "forgejo_user" = [
              "profile"
              "email"
              "openid"
            ];
          };
          claimMaps.groups = {
            joinType = "array";
            valuesByGroup = {
              "forgejo_comitter" = [
                "comitter"
              ];
              "forgejo_admin" = [
                "admin"
              ];
            };
          };
        };
      };
    };
  };
  # Don't add dependencies from bootstrapping when not bootstrapping.
  systemd.services.kanidm = lib.mkIf (!bootstrapping) {
    unitConfig = {
      ConditionPathExists = [
        openbaoAppBasicSecretFile
      ];
    };
  };

  # Allow the server to read the secrets for its own apps
  khscodes.infrastructure.vault-server-approle.policy."kanidm/data/apps/*" = {
    capabilities = [ "read" ];
  };

  khscodes.infrastructure.provisioning.post.modules = [
    # Creates mount for kanidm apps and generates random passwords for the apps
    (
      { config, ... }:
      {
        terraform.required_providers.random = {
          source = "hashicorp/random";
          version = "3.7.2";
        };
        provider.random = { };
        # NOTE: This gets stored in the state file
        resource.random_password.openbao_secret = {
          length = 48;
          numeric = true;
          lower = true;
          upper = true;
          special = false;
        };
        khscodes.vault = {
          enable = true;
          mount.kanidm = {
            path = "kanidm";
            type = "kv";
            options = {
              version = "2";
            };
            description = "Secrets used for kanidm";
          };
        };
        resource.vault_kv_secret_v2.openbao_secret = {
          mount = config.khscodes.vault.output.mount.kanidm.path;
          name = "apps/openbao";
          data_json = ''
            { "basic_secret": "''${ resource.random_password.openbao_secret.result }" }
          '';
        };
        resource.random_password.openbao_cli_secret = {
          length = 48;
          numeric = true;
          lower = true;
          upper = true;
          special = false;
        };
        resource.vault_kv_secret_v2.openbao_cli_secret = {
          mount = config.khscodes.vault.output.mount.kanidm.path;
          name = "apps/openbao_cli";
          data_json = ''
            { "basic_secret": "''${ resource.random_password.openbao_cli_secret.result }" }
          '';
        };
        resource.random_password.monitoring = {
          length = 48;
          numeric = true;
          lower = true;
          upper = true;
          special = false;
        };
        resource.vault_kv_secret_v2.monitoring_secret = {
          mount = config.khscodes.vault.output.mount.kanidm.path;
          name = "apps/monitoring";
          data_json = ''
            { "basic_secret": "''${ resource.random_password.monitoring.result }" }
          '';
        };
        resource.random_password.forgejo = {
          length = 48;
          numeric = true;
          lower = true;
          upper = true;
          special = false;
        };
        resource.vault_kv_secret_v2.forgejo_secret = {
          mount = config.khscodes.vault.output.mount.kanidm.path;
          name = "apps/forgejo";
          data_json = ''
            { "basic_secret": "''${ resource.random_password.forgejo.result }" }
          '';
        };
      }
    )
    # Sets up OIDC authentication within OpenBAO.
    # OpenBAO queries the openid url for its configuration when adding it, so it is not possible,
    # to add this before
    (
      if bootstrapping then
        { }
      else
        {
          resource.vault_jwt_auth_backend.kanidm_cli = {
            description = "Kanidm cli auth backend";
            path = "oidc";
            type = "oidc";
            oidc_discovery_url = "https://${domain}/oauth2/openid/openbao-cli";
            oidc_client_id = "openbao-cli";
            oidc_client_secret = "\${ resource.random_password.openbao_cli_secret.result }";
            default_role = "kanidm_cli_writer";
            jwt_supported_algs = [
              "ES256"
            ];
            tune = [
              {
                listing_visibility = "hidden";
                default_lease_ttl = "2h";
                max_lease_ttl = "2h";
                token_type = "default-service";
                passthrough_request_headers = [ ];
                allowed_response_headers = [ ];
                audit_non_hmac_request_keys = [ ];
                audit_non_hmac_response_keys = [ ];
              }
            ];
          };
          resource.vault_jwt_auth_backend.kanidm = {
            description = "Kanidm auth backend";
            path = "kanidm";
            type = "oidc";
            oidc_discovery_url = "https://${domain}/oauth2/openid/openbao";
            oidc_client_id = "openbao";
            oidc_client_secret = "\${ resource.random_password.openbao_secret.result }";
            default_role = "kanidm_writer";
            jwt_supported_algs = [
              "ES256"
            ];
            tune = [
              {
                listing_visibility = "unauth";
                default_lease_ttl = "2h";
                max_lease_ttl = "2h";
                token_type = "default-service";
                passthrough_request_headers = [ ];
                allowed_response_headers = [ ];
                audit_non_hmac_request_keys = [ ];
                audit_non_hmac_response_keys = [ ];
              }
            ];
          };

          resource.vault_jwt_auth_backend_role.kanidm_cli_writer = {
            backend = "\${ resource.vault_jwt_auth_backend.kanidm_cli.path }";
            role_name = "kanidm_cli_writer";
            bound_audiences = [ "openbao-cli" ];
            allowed_redirect_uris = [ "http://localhost:8250/oidc/callback" ];
            user_claim = "sub";
            token_policies = [ "writer" ];
            groups_claim = "groups";
            oidc_scopes = [
              "openid"
              "profile"
              "email"
            ];
          };

          resource.vault_jwt_auth_backend_role.kanidm_writer = {
            backend = "\${ resource.vault_jwt_auth_backend.kanidm.path }";
            role_name = "kanidm_writer";
            bound_audiences = [ "openbao" ];
            allowed_redirect_uris = openbaoAllowedRedirectUrls;
            user_claim = "sub";
            token_policies = [ "writer" ];
            groups_claim = "groups";
            oidc_scopes = [
              "openid"
              "profile"
              "email"
            ];
          };

          resource.vault_identity_group.kanidm_writer = {
            name = "kanidm_writer";
            policies = [ "writer" ];
            type = "external";
          };

          resource.vault_identity_group.kanidm_cli_writer = {
            name = "kanidm_cli_writer";
            policies = [ "writer" ];
            type = "external";
          };

          resource.vault_identity_group_alias.oidc_writer = {
            name = "openbao_cli_writer";
            mount_accessor = "\${ vault_jwt_auth_backend.kanidm_cli.accessor }";
            canonical_id = "\${ vault_identity_group.kanidm_cli_writer.id }";
          };

          resource.vault_identity_group_alias.kanidm_writer = {
            name = "openbao_writer";
            mount_accessor = "\${ vault_jwt_auth_backend.kanidm.accessor }";
            canonical_id = "\${ vault_identity_group.kanidm_writer.id }";
          };

          resource.vault_policy.writer = {
            name = "writer";

            policy = ''
              path "*" {
                capabilities = ["create", "update", "patch", "read", "delete", "list"]
              }
            '';
          };
        }
    )
  ];

  khscodes.services.vault-agent.templates = [
    {
      contents = ''
        {{- with secret "kanidm/data/apps/openbao" -}}
        {{ .Data.data.basic_secret }}
        {{- end -}}
      '';
      destination = openbaoAppBasicSecretFile;
      perms = "0600";
      owner = "kanidm";
      group = "kanidm";
      reloadOrRestartUnits = [ "kanidm.service" ];
    }
    {
      contents = ''
        {{- with secret "kanidm/data/apps/openbao_cli" -}}
        {{ .Data.data.basic_secret }}
        {{- end -}}
      '';
      destination = openbaoCliAppBasicSecretFile;
      perms = "0600";
      owner = "kanidm";
      group = "kanidm";
      reloadOrRestartUnits = [ "kanidm.service" ];
    }
    {
      contents = ''
        {{- with secret "kanidm/data/apps/monitoring" -}}
        {{ .Data.data.basic_secret }}
        {{- end -}}
      '';
      destination = monitoringAppBasicSecretFile;
      perms = "0600";
      owner = "kanidm";
      group = "kanidm";
      reloadOrRestartUnits = [ "kanidm.service" ];
    }
    {
      contents = ''
        {{- with secret "kanidm/data/apps/forgejo" -}}
        {{ .Data.data.basic_secret }}
        {{- end -}}
      '';
      destination = forgejoAppBasicSecretFile;
      perms = "0600";
      owner = "kanidm";
      group = "kanidm";
      reloadOrRestartUnits = [ "kanidm.service" ];
    }
  ];

  security.acme.certs.${domain}.reloadServices = [ "kanidm.service" ];
  # Allow kanidm to read the certificate file
  users.groups.nginx.members = [ "kanidm" ];

  khscodes.services.nginx.virtualHosts.${domain} = {
    locations."/" = {
      proxyPass = "https://${config.services.kanidm.serverSettings.bindaddress}/";
      recommendedProxySettings = true;
    };
  };

  environment.systemPackages = [
    kanidm-reset-password
  ];
}
