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

let
  cfg = config.services.coronafs;
  chainfireEnabled = lib.hasAttrByPath [ "services" "chainfire" "enable" ] config && config.services.chainfire.enable;
  chainfireApiUrls =
    if cfg.chainfireApiUrl != null then
      lib.filter (item: item != "") (map lib.strings.trim (lib.splitString "," cfg.chainfireApiUrl))
    else
      [ ];
  effectiveChainfireApiUrl =
    if cfg.chainfireApiUrl != null then cfg.chainfireApiUrl
    else if chainfireEnabled then "http://127.0.0.1:${toString config.services.chainfire.httpPort}"
    else null;
  localChainfireApiUrl =
    lib.any
      (url:
        lib.hasPrefix "http://127.0.0.1:" url
        || lib.hasPrefix "http://localhost:" url
      )
      (
        if effectiveChainfireApiUrl == null then
          [ ]
        else if cfg.chainfireApiUrl != null then
          chainfireApiUrls
        else
          [ effectiveChainfireApiUrl ]
      );
  waitForChainfire =
    pkgs.writeShellScript "coronafs-wait-for-chainfire" ''
      set -eu
      deadline=$((SECONDS + 60))
      urls='${lib.concatStringsSep " " (
        if effectiveChainfireApiUrl == null then
          [ ]
        else if cfg.chainfireApiUrl != null then
          chainfireApiUrls
        else
          [ effectiveChainfireApiUrl ]
      )}'
      while true; do
        for url in $urls; do
          if curl -fsS "$url/health" >/dev/null 2>&1; then
            exit 0
          fi
        done
        if [ "$SECONDS" -ge "$deadline" ]; then
          echo "timed out waiting for ChainFire at ${if effectiveChainfireApiUrl == null then "(none)" else effectiveChainfireApiUrl}" >&2
          exit 1
        fi
        sleep 1
      done
    '';
  tomlFormat = pkgs.formats.toml { };
  coronafsConfigFile = tomlFormat.generate "coronafs.toml" (
    {
      mode = cfg.mode;
      metadata_backend = cfg.metadataBackend;
      chainfire_key_prefix = cfg.chainfireKeyPrefix;
      listen_addr = "0.0.0.0:${toString cfg.port}";
      advertise_host = cfg.advertiseHost;
      data_dir = toString cfg.dataDir;
      export_bind_addr = cfg.exportBindAddr;
      export_base_port = cfg.exportBasePort;
      export_port_count = cfg.exportPortCount;
      export_shared_clients = cfg.exportSharedClients;
      export_cache_mode = cfg.exportCacheMode;
      export_aio_mode = cfg.exportAioMode;
      export_discard_mode = cfg.exportDiscardMode;
      export_detect_zeroes_mode = cfg.exportDetectZeroesMode;
      preallocate = cfg.preallocate;
      sync_on_write = cfg.syncOnWrite;
      qemu_nbd_path = "${pkgs.qemu}/bin/qemu-nbd";
      qemu_img_path = "${pkgs.qemu}/bin/qemu-img";
      log_level = "info";
    }
    // lib.optionalAttrs (effectiveChainfireApiUrl != null) {
      chainfire_api_url = effectiveChainfireApiUrl;
    }
  );
in
{
  options.services.coronafs = {
    enable = lib.mkEnableOption "CoronaFS block volume service";

    mode = lib.mkOption {
      type = lib.types.enum [ "combined" "controller" "node" ];
      default = "combined";
      description = "CoronaFS operating mode: combined compatibility mode, controller-only API, or node-local export mode.";
    };

    metadataBackend = lib.mkOption {
      type = lib.types.enum [ "filesystem" "chainfire" ];
      default = "filesystem";
      description = "Metadata backend for CoronaFS volume metadata. Use chainfire on controller nodes to replicate volume metadata.";
    };

    chainfireApiUrl = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = null;
      description = "Optional ChainFire HTTP API URL used when metadataBackend = chainfire. Comma-separated endpoints are allowed for failover.";
      example = "http://127.0.0.1:8081";
    };

    chainfireKeyPrefix = lib.mkOption {
      type = lib.types.str;
      default = "/coronafs/volumes";
      description = "ChainFire key prefix used to store CoronaFS metadata when metadataBackend = chainfire.";
    };

    port = lib.mkOption {
      type = lib.types.port;
      default = 50088;
      description = "Port for the CoronaFS control API.";
    };

    advertiseHost = lib.mkOption {
      type = lib.types.str;
      default = "127.0.0.1";
      description = "Host or IP placed into exported NBD URIs.";
      example = "10.0.0.11";
    };

    exportBindAddr = lib.mkOption {
      type = lib.types.str;
      default = "0.0.0.0";
      description = "Bind address for qemu-nbd exports.";
    };

    exportBasePort = lib.mkOption {
      type = lib.types.port;
      default = 11000;
      description = "First TCP port reserved for CoronaFS NBD exports.";
    };

    exportPortCount = lib.mkOption {
      type = lib.types.int;
      default = 512;
      description = "Number of NBD export ports reserved for CoronaFS volumes.";
    };

    exportSharedClients = lib.mkOption {
      type = lib.types.int;
      default = 32;
      description = "Maximum number of concurrent clients per exported CoronaFS volume.";
    };

    exportCacheMode = lib.mkOption {
      type = lib.types.enum [ "none" "writeback" "writethrough" "directsync" "unsafe" ];
      default = "none";
      description = "qemu-nbd cache mode for CoronaFS exports.";
    };

    exportAioMode = lib.mkOption {
      type = lib.types.enum [ "native" "io_uring" "threads" ];
      default = "threads";
      description = "qemu-nbd AIO mode for CoronaFS exports.";
    };

    exportDiscardMode = lib.mkOption {
      type = lib.types.enum [ "ignore" "unmap" ];
      default = "unmap";
      description = "qemu-nbd discard handling for CoronaFS exports.";
    };

    exportDetectZeroesMode = lib.mkOption {
      type = lib.types.enum [ "off" "on" "unmap" ];
      default = "unmap";
      description = "qemu-nbd detect-zeroes mode for CoronaFS exports.";
    };

    preallocate = lib.mkOption {
      type = lib.types.bool;
      default = true;
      description = "Preallocate blank CoronaFS volumes with fallocate when possible.";
    };

    syncOnWrite = lib.mkOption {
      type = lib.types.bool;
      default = false;
      description = "Force sync_all after volume import writes.";
    };

    dataDir = lib.mkOption {
      type = lib.types.path;
      default = "/var/lib/coronafs";
      description = "Data directory for CoronaFS volumes, metadata, and export pid files.";
    };

    package = lib.mkOption {
      type = lib.types.package;
      default = pkgs.coronafs-server or (throw "coronafs-server package not found");
      description = "Package to use for CoronaFS.";
    };
  };

  config = lib.mkIf cfg.enable {
    assertions = [
      {
        assertion = cfg.metadataBackend != "chainfire" || effectiveChainfireApiUrl != null;
        message = "services.coronafs.metadataBackend = \"chainfire\" requires services.coronafs.chainfireApiUrl or a local services.chainfire instance.";
      }
    ];

    users.users.coronafs = {
      isSystemUser = true;
      group = "coronafs";
      description = "CoronaFS service user";
      home = cfg.dataDir;
      extraGroups =
        lib.optional
          (lib.hasAttrByPath [ "services" "plasmavmc" "enable" ] config && config.services.plasmavmc.enable)
          "plasmavmc";
    };

    users.groups.coronafs = { };

    systemd.services.coronafs = {
      description = "CoronaFS Block Volume Service";
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ] ++ lib.optionals chainfireEnabled [ "chainfire.service" ];
      wants = lib.optionals chainfireEnabled [ "chainfire.service" ];
      path = [ pkgs.qemu pkgs.util-linux pkgs.procps pkgs.coreutils pkgs.curl ];

      serviceConfig = {
        Type = "simple";
        User = "coronafs";
        Group = "coronafs";
        UMask = "0007";
        Restart = "on-failure";
        RestartSec = "5s";
        StateDirectory = "coronafs";
        StateDirectoryMode = "0750";
        ReadWritePaths = [ cfg.dataDir ];
        ExecStartPre = lib.optionals (cfg.metadataBackend == "chainfire" && localChainfireApiUrl) [ waitForChainfire ];
        ExecStart = "${cfg.package}/bin/coronafs-server --config ${coronafsConfigFile}";
      };
    };

    systemd.tmpfiles.rules = [
      "d ${toString cfg.dataDir} 0750 coronafs coronafs -"
      "d ${toString cfg.dataDir}/volumes 2770 coronafs coronafs -"
      "d ${toString cfg.dataDir}/metadata 0750 coronafs coronafs -"
      "d ${toString cfg.dataDir}/pids 0750 coronafs coronafs -"
    ];
  };
}
