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

let
  contributors = import ../../../contributors.nix;
  identities = contributors.identities;
  linearGroups = contributors.groups.linear;
  stripNewline = value: lib.replaceStrings [ "\n" ] [ "" ] value;
  authentikPasswordSecretPath = identity:
    if identity ? authentikPasswordSecret
    then config.age.secrets.${identity.authentikPasswordSecret}.path
    else null;
  bootstrapUsers = lib.mapAttrsToList
    (
      username: identity: {
        inherit username;
        name = identity.displayName;
        email = identity.canonicalEmail;
        isAdmin = identity.isAdmin or false;
        groups = lib.optionals (identity.isAdmin or false) [ linearGroups.owners ];
        passwordFile = authentikPasswordSecretPath identity;
      }
    )
    (lib.filterAttrs (_: identity: identity.bootstrapAuthentik or false) identities);
  headscaleBootstrapUsers = lib.mapAttrsToList
    (
      username: identity: {
        name = username;
        displayName = identity.displayName;
        email = identity.canonicalEmail;
      }
    )
    (lib.filterAttrs (_: identity: identity.bootstrapAuthentik or false) identities);
  forgeUnixUsernames =
    builtins.attrNames (lib.filterAttrs (_: identity: identity.forgeUnixUser or false) identities);
  forgeUnixUsers = lib.genAttrs forgeUnixUsernames (username:
    let
      identity = identities.${username};
      sshKeys = lib.optional (identity ? sshPublicKeyPath) (stripNewline (builtins.readFile identity.sshPublicKeyPath));
    in
    {
      isNormalUser = true;
      createHome = true;
      home = "/home/${username}";
      shell = pkgs.bashInteractive;
      extraGroups = lib.optional (identity.isAdmin or false) "wheel";
      openssh.authorizedKeys.keys = sshKeys;
    });
  forgeUnixAdminUsernames =
    builtins.attrNames (lib.filterAttrs (_: identity: (identity.forgeUnixUser or false) && (identity.isAdmin or false)) identities);
  forgeAuthorizedKeys = map
    (username: builtins.readFile identities.${username}.sshPublicKeyPath)
    (builtins.attrNames (lib.filterAttrs (_: identity: identity.forgeAuthorized or false) identities));
  forgejoNscMaterializeTokenCache = ''
    install -d -m 700 -o forgejo-nsc -g forgejo-nsc /var/lib/forgejo-nsc/.config/ns
    ${pkgs.python3}/bin/python3 - <<'PY'
import json
from pathlib import Path

raw = Path("/var/lib/forgejo-nsc/nsc.token").read_text(encoding="utf-8").strip()
if not raw:
    raise SystemExit("empty Namespace token")
if raw.startswith("{"):
    data = json.loads(raw)
    if "bearer_token" not in data and "session_token" not in data:
        raise SystemExit("Namespace token JSON must contain bearer_token or session_token")
else:
    data = {"session_token": raw}
Path("/var/lib/forgejo-nsc/.config/ns/token.json").write_text(
    json.dumps(data) + "\n",
    encoding="utf-8",
)
PY
    chown forgejo-nsc:forgejo-nsc /var/lib/forgejo-nsc/.config/ns/token.json
    chmod 600 /var/lib/forgejo-nsc/.config/ns/token.json
  '';
in

{
  imports = [
    ./hardware-configuration.nix
    ./disko-config.nix
    self.nixosModules.burrow-forge
    self.nixosModules.burrow-forge-runner
    self.nixosModules.burrow-forgejo-nsc
    self.nixosModules.burrow-authentik
    self.nixosModules.burrow-garage
    self.nixosModules.burrow-headscale
    self.nixosModules.burrow-nix-cache
    self.nixosModules.burrow-observability
    self.nixosModules.burrow-zulip
    self.nixosModules.burrow-openbao
    self.nixosModules.burrow-jitsi
  ];

  system.stateVersion = "24.11";

  time.timeZone = "America/Los_Angeles";

  nix.settings.experimental-features = [
    "nix-command"
    "flakes"
  ];

  users.users = forgeUnixUsers;

  security.sudo.extraRules = lib.map (username: {
    users = [ username ];
    commands = [
      {
        command = "ALL";
        options = [ "NOPASSWD" ];
      }
    ];
  }) forgeUnixAdminUsernames;

  environment.systemPackages = lib.optionals config.services.forgejo-nsc.enable [
    self.packages.${pkgs.stdenv.hostPlatform.system}.nsc
  ];

  age.identityPaths = [ "/var/lib/agenix/agenix.key" ];
  age.secrets.burrowAuthentikEnv = {
    file = ../../../secrets/infra/authentik.env.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };
  age.secrets.burrowHeadscaleOidcClientSecret = {
    file = ../../../secrets/infra/headscale-oidc-client-secret.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };
  age.secrets.burrowForgejoOidcClientSecret = {
    file = ../../../secrets/infra/forgejo-oidc-client-secret.age;
    owner = "forgejo";
    group = "forgejo";
    mode = "0440";
  };
  age.secrets.burrowGrafanaAdminPassword = {
    file = ../../../secrets/infra/grafana-admin-password.age;
    owner = "grafana";
    group = "grafana";
    mode = "0400";
  };
  age.secrets.burrowGrafanaSecretKey = {
    file = ../../../secrets/infra/grafana-secret-key.age;
    owner = "grafana";
    group = "grafana";
    mode = "0400";
  };
  age.secrets.burrowGrafanaOidcClientSecret = {
    file = ../../../secrets/infra/grafana-oidc-client-secret.age;
    owner = "grafana";
    group = "grafana";
    mode = "0400";
  };
  age.secrets.burrowTailscaleOidcClientSecret = {
    file = ../../../secrets/infra/tailscale-oidc-client-secret.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };
  age.secrets.burrowLinearScimToken = {
    file = ../../../secrets/infra/linear-scim-token.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };
  age.secrets.burrowAuthentikGoogleClientId = {
    file = ../../../secrets/infra/authentik-google-client-id.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };
  age.secrets.burrowAuthentikGoogleClientSecret = {
    file = ../../../secrets/infra/authentik-google-client-secret.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };
  age.secrets.burrowAuthentikGoogleAccountMap = {
    file = ../../../secrets/infra/authentik-google-account-map.json.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };
  age.secrets.burrowAuthentikGoogleWifClientSecret = {
    file = ../../../secrets/infra/authentik-google-wif-client-secret.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };
  age.secrets.burrowAuthentikUiTestPassword = {
    file = ../../../secrets/infra/authentik-ui-test-password.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };
  age.secrets.burrowForgejoNscToken = {
    file = ../../../secrets/infra/forgejo-nsc-token.age;
    owner = "forgejo-nsc";
    group = "forgejo-nsc";
    mode = "0400";
  };
  age.secrets.burrowForgejoNscDispatcherConfig = {
    file = ../../../secrets/infra/forgejo-nsc-dispatcher-config.age;
    owner = "forgejo-nsc";
    group = "forgejo-nsc";
    mode = "0400";
  };
  age.secrets.burrowForgejoNscAutoscalerConfig = {
    file = ../../../secrets/infra/forgejo-nsc-autoscaler-config.age;
    owner = "forgejo-nsc";
    group = "forgejo-nsc";
    mode = "0400";
  };

  age.secrets.burrowZulipPostgresPassword = {
    file = ../../../secrets/infra/zulip-postgres-password.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };

  age.secrets.burrowZulipRabbitmqPassword = {
    file = ../../../secrets/infra/zulip-rabbitmq-password.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };

  age.secrets.burrowZulipRedisPassword = {
    file = ../../../secrets/infra/zulip-redis-password.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };

  age.secrets.burrowZulipSecretKey = {
    file = ../../../secrets/infra/zulip-secret-key.age;
    owner = "root";
    group = "root";
    mode = "0400";
  };

  networking.extraHosts = ''
    127.0.0.1 burrow.net git.burrow.net auth.burrow.net ts.burrow.net chat.burrow.net graphs.burrow.net vault.burrow.net meet.burrow.net inbox.burrow.net nsc-autoscaler.burrow.net
    ::1 burrow.net git.burrow.net auth.burrow.net ts.burrow.net chat.burrow.net graphs.burrow.net vault.burrow.net meet.burrow.net inbox.burrow.net nsc-autoscaler.burrow.net
  '';

  services.burrow.forge = {
    enable = true;
    contactEmail = identities.contact.canonicalEmail;
    adminUsername = "contact";
    adminEmail = identities.contact.canonicalEmail;
    adminPasswordFile = "/var/lib/burrow/intake/forgejo_pass_contact_at_burrow_net.txt";
    oidcAdminGroup = contributors.groups.admins;
    oidcRestrictedGroup = contributors.groups.users;
    oidcClientSecretFile = config.age.secrets.burrowForgejoOidcClientSecret.path;
    authorizedKeys = forgeAuthorizedKeys;
  };

  services.burrow.forgeRunner = {
    enable = true;
    sshPrivateKeyFile = "/var/lib/burrow/intake/agent_at_burrow_net_ed25519";
    labels = [
      "self-hosted"
      "linux"
      "x86_64"
      "burrow-forge"
    ];
  };

  services.forgejo-nsc = {
    enable = true;
    nscPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.nsc;
    nscTokenFile = config.age.secrets.burrowForgejoNscToken.path;
    dispatcher = {
      package = self.packages.${pkgs.stdenv.hostPlatform.system}.forgejo-nsc-dispatcher;
      configFile = config.age.secrets.burrowForgejoNscDispatcherConfig.path;
    };
    autoscaler = {
      enable = true;
      package = self.packages.${pkgs.stdenv.hostPlatform.system}.forgejo-nsc-autoscaler;
      configFile = config.age.secrets.burrowForgejoNscAutoscalerConfig.path;
    };
  };

  systemd.services.forgejo-nsc-dispatcher.preStart = lib.mkAfter forgejoNscMaterializeTokenCache;
  systemd.services.forgejo-nsc-autoscaler.preStart = lib.mkAfter forgejoNscMaterializeTokenCache;

  services.burrow.authentik = {
    enable = true;
    envFile = config.age.secrets.burrowAuthentikEnv.path;
    forgejoClientSecretFile = config.age.secrets.burrowForgejoOidcClientSecret.path;
    grafanaClientSecretFile = config.age.secrets.burrowGrafanaOidcClientSecret.path;
    grafanaAccessGroupName = contributors.groups.users;
    headscaleClientSecretFile = config.age.secrets.burrowHeadscaleOidcClientSecret.path;
    tailscaleClientSecretFile = config.age.secrets.burrowTailscaleOidcClientSecret.path;
    defaultExternalApplicationSlug = "tailscale";
    googleClientIDFile = config.age.secrets.burrowAuthentikGoogleClientId.path;
    googleClientSecretFile = config.age.secrets.burrowAuthentikGoogleClientSecret.path;
    googleAccountMapFile = config.age.secrets.burrowAuthentikGoogleAccountMap.path;
    googleWifClientSecretFile = config.age.secrets.burrowAuthentikGoogleWifClientSecret.path;
    googleLoginMode = "redirect";
    userGroupName = contributors.groups.users;
    adminGroupName = contributors.groups.admins;
    extraGroupNames = [ contributors.groups.automation ];
    tailscaleAccessGroupName = contributors.groups.users;
    bootstrapUsers = bootstrapUsers;
    linearAcsUrl = "https://api.linear.app/auth/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de/acs";
    linearAudience = "https://auth.linear.app/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de";
    linearDefaultRelayState = "https://linear.app/auth/sso/d0ca13dc-ac41-4824-8aab-e0ca352fc3de";
    linearScimUrl = "https://api.linear.app/auth/scim/d0ca13dc-ac41-4824-8aab-e0ca352fc3de";
    linearScimTokenFile = config.age.secrets.burrowLinearScimToken.path;
    linearScimUserIdentifier = "email";
    linearOwnerGroupName = linearGroups.owners;
    linearAdminGroupName = linearGroups.admins;
    linearGuestGroupName = linearGroups.guests;
    zulipAccessGroupName = contributors.groups.users;
  };

  services.grafana = {
    enable = true;
    settings = {
      server = {
        domain = "graphs.burrow.net";
        root_url = "https://graphs.burrow.net/";
        http_addr = "127.0.0.1";
        http_port = 3001;
        enforce_domain = true;
      };
      analytics = {
        reporting_enabled = false;
        check_for_updates = false;
        check_for_plugin_updates = false;
      };
      security = {
        admin_user = "admin";
        admin_password = "$__file{${config.age.secrets.burrowGrafanaAdminPassword.path}}";
        secret_key = "$__file{${config.age.secrets.burrowGrafanaSecretKey.path}}";
        cookie_secure = true;
        cookie_samesite = "strict";
        disable_gravatar = true;
      };
      users = {
        allow_sign_up = false;
        auto_assign_org = true;
        auto_assign_org_role = "Viewer";
      };
      auth = {
        disable_login_form = false;
        oauth_auto_login = false;
      };
      "auth.generic_oauth" = {
        enabled = true;
        name = "burrow.net";
        allow_sign_up = true;
        auto_login = true;
        client_id = "graphs.burrow.net";
        client_secret = "$__file{${config.age.secrets.burrowGrafanaOidcClientSecret.path}}";
        scopes = "openid profile email groups";
        auth_url = "https://auth.burrow.net/application/o/grafana/authorize/";
        token_url = "https://auth.burrow.net/application/o/grafana/token/";
        api_url = "https://auth.burrow.net/application/o/grafana/userinfo/";
        role_attribute_path = "contains(groups[*], 'burrow-admins') && 'Admin' || 'Viewer'";
      };
    };
  };

  services.caddy.virtualHosts."graphs.burrow.net".extraConfig = ''
    encode gzip zstd
    reverse_proxy 127.0.0.1:3001
  '';

  services.caddy.virtualHosts."releases.burrow.net".extraConfig = ''
    encode gzip zstd
    rewrite * /burrow-net-releases{uri}
    reverse_proxy https://storage.googleapis.com {
      header_up Host storage.googleapis.com
    }
  '';

  services.caddy.virtualHosts."packages.burrow.net".extraConfig = ''
    encode gzip zstd
    rewrite * /burrow-net-packages{uri}
    reverse_proxy https://storage.googleapis.com {
      header_up Host storage.googleapis.com
    }
  '';

  services.burrow.headscale = {
    enable = true;
    oidcClientSecretFile = config.age.secrets.burrowHeadscaleOidcClientSecret.path;
    bootstrapUsers = headscaleBootstrapUsers;
  };

  services.burrow.garage = {
    enable = true;
    enableApiProxy = true;
    enableWebProxy = true;
  };

  services.burrow.nixCache.enable = true;

  services.burrow.observability.enable = true;

  services.burrow.zulip = {
    enable = true;
    administratorEmail = identities.contact.canonicalEmail;
    postgresPasswordFile = config.age.secrets.burrowZulipPostgresPassword.path;
    rabbitmqPasswordFile = config.age.secrets.burrowZulipRabbitmqPassword.path;
    redisPasswordFile = config.age.secrets.burrowZulipRedisPassword.path;
    secretKeyFile = config.age.secrets.burrowZulipSecretKey.path;
  };
}
