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

let
  cfg = config.services.burrow.nixCache;
  garageCfg = config.services.burrow.garage;
in
{
  options.services.burrow.nixCache = {
    enable = lib.mkEnableOption "Burrow Nix binary cache";

    domain = lib.mkOption {
      type = lib.types.str;
      default = "nix.burrow.net";
      description = "Public domain for the Burrow Nix binary cache.";
    };

    listenAddress = lib.mkOption {
      type = lib.types.str;
      default = "127.0.0.1";
      description = "Attic listen address.";
    };

    port = lib.mkOption {
      type = lib.types.port;
      default = 8080;
      description = "Attic listen port.";
    };

    cacheName = lib.mkOption {
      type = lib.types.str;
      default = "burrow";
      description = "Default public Attic cache name.";
    };

    environmentFile = lib.mkOption {
      type = lib.types.path;
      default = garageCfg.environmentFile;
      description = "Environment file containing Attic JWT and Garage S3 credentials.";
    };

    adminTokenFile = lib.mkOption {
      type = lib.types.path;
      default = "/var/lib/burrow/nix-cache/admin-token";
      description = "Host-local file containing the generated Attic admin token.";
    };

    ciPushTokenFile = lib.mkOption {
      type = lib.types.path;
      default = "/var/lib/burrow/nix-cache/ci-push-token";
      description = "Host-local file containing the generated Attic CI push token.";
    };

    ciPushTokenValidity = lib.mkOption {
      type = lib.types.str;
      default = "1y";
      description = "Validity duration for the generated Attic CI push token.";
    };
  };

  config = lib.mkIf cfg.enable {
    assertions = [
      {
        assertion = garageCfg.enable;
        message = "services.burrow.nixCache requires services.burrow.garage.enable = true.";
      }
    ];

    services.atticd = {
      enable = true;
      environmentFile = cfg.environmentFile;
      settings = {
        listen = "${cfg.listenAddress}:${toString cfg.port}";
        allowed-hosts = [ cfg.domain ];
        api-endpoint = "https://${cfg.domain}/";
        require-proof-of-possession = true;
        storage = {
          type = "s3";
          bucket = garageCfg.atticBucket;
          region = garageCfg.region;
          endpoint = "http://${garageCfg.apiListenAddress}:${toString garageCfg.apiPort}";
        };
      };
    };

    environment.systemPackages = [
      pkgs.attic-client
    ];

    services.caddy.virtualHosts."${cfg.domain}".extraConfig = ''
      encode gzip zstd
      reverse_proxy ${cfg.listenAddress}:${toString cfg.port}
    '';

    systemd.services.atticd = {
      after = [ "burrow-garage-bootstrap.service" ];
      wants = [ "burrow-garage-bootstrap.service" ];
      requires = [ "burrow-garage-bootstrap.service" ];
    };

    systemd.services.burrow-nix-cache-bootstrap = {
      description = "Bootstrap Burrow Nix binary cache";
      after = [ "atticd.service" ];
      requires = [ "atticd.service" ];
      wantedBy = [ "multi-user.target" ];
      path = [
        pkgs.attic-client
        pkgs.attic-server
        pkgs.coreutils
      ];
      serviceConfig = {
        Type = "oneshot";
        User = "root";
        Group = "root";
      };
      script = ''
        set -euo pipefail

        install -d -m 0700 "$(dirname ${lib.escapeShellArg cfg.adminTokenFile})"
        install -d -m 0700 "$(dirname ${lib.escapeShellArg cfg.ciPushTokenFile})"

        for _ in $(seq 1 60); do
          if (: > /dev/tcp/${cfg.listenAddress}/${toString cfg.port}) >/dev/null 2>&1; then
            break
          fi
          sleep 1
        done

        if [ ! -s ${lib.escapeShellArg cfg.adminTokenFile} ]; then
          umask 077
          atticadm make-token \
            --sub burrow-nix-cache-bootstrap \
            --validity 10y \
            --create-cache '*' \
            --pull '*' \
            --push '*' \
            --delete '*' \
            --configure-cache '*' \
            --configure-cache-retention '*' \
            > ${lib.escapeShellArg cfg.adminTokenFile}
        fi

        token="$(cat ${lib.escapeShellArg cfg.adminTokenFile})"
        export HOME=/var/lib/burrow/nix-cache
        install -d -m 0700 "$HOME"

        attic login local http://${cfg.listenAddress}:${toString cfg.port} "$token" --set-default
        if ! attic cache info ${lib.escapeShellArg "local:${cfg.cacheName}"} >/dev/null 2>&1; then
          attic cache create --public --priority 39 ${lib.escapeShellArg "local:${cfg.cacheName}"}
        fi

        if [ ! -s ${lib.escapeShellArg cfg.ciPushTokenFile} ]; then
          umask 077
          atticadm make-token \
            --sub burrow-ci-cache-publisher \
            --validity ${lib.escapeShellArg cfg.ciPushTokenValidity} \
            --pull ${lib.escapeShellArg cfg.cacheName} \
            --push ${lib.escapeShellArg cfg.cacheName} \
            > ${lib.escapeShellArg cfg.ciPushTokenFile}
        fi
      '';
    };
  };
}
