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

let
  cfg = config.services.chainfire;
  tomlFormat = pkgs.formats.toml { };
  stripLeadingZeros = digits:
    if digits == "" then ""
    else if lib.hasPrefix "0" digits then stripLeadingZeros (lib.removePrefix "0" digits)
    else digits;
  numericIdString = value:
    let
      captures = builtins.match ".*?([0-9]+)$" value;
      digits =
        if captures == null
        then throw "services.chainfire.nodeId must end with digits (got '${value}')"
        else builtins.elemAt captures 0;
      normalized = stripLeadingZeros digits;
    in
    if normalized == "" then "0" else normalized;
  numericId = value: builtins.fromJSON (numericIdString value);
  hostFromAddr = addr:
    let captures = builtins.match "(.+):[0-9]+" addr;
    in if captures == null then null else builtins.elemAt captures 0;
  apiAddrArg =
    if cfg.apiAddr != null
    then cfg.apiAddr
    else "0.0.0.0:${toString cfg.port}";
  raftAddrArg =
    if cfg.raftAddr != null
    then cfg.raftAddr
    else "0.0.0.0:${toString cfg.raftPort}";
  gossipAddrArg =
    if cfg.gossipAddr != null
    then cfg.gossipAddr
    else
      let host = hostFromAddr apiAddrArg;
      in if host != null then "${host}:${toString cfg.gossipPort}" else "0.0.0.0:${toString cfg.gossipPort}";
  initialMembers = map
    (peer:
      let
        parts = lib.splitString "=" peer;
        rawId =
          if builtins.length parts == 2
          then builtins.elemAt parts 0
          else throw "services.chainfire.initialPeers entries must be 'nodeId=host:port' (got '${peer}')";
        raftAddr = builtins.elemAt parts 1;
      in {
        id = numericId rawId;
        raft_addr = raftAddr;
      })
    cfg.initialPeers;
  generatedConfig = {
    node = {
      id = numericId cfg.nodeId;
      name = cfg.nodeId;
      role = cfg.role;
    };
    storage = {
      data_dir = toString cfg.dataDir;
    };
    network = {
      api_addr = apiAddrArg;
      http_addr = "0.0.0.0:${toString cfg.httpPort}";
      raft_addr = raftAddrArg;
      gossip_addr = gossipAddrArg;
    };
    cluster = {
      id = cfg.clusterId;
      initial_members = initialMembers;
      bootstrap = cfg.bootstrap;
    };
    raft = {
      role = cfg.raftRole;
    };
  };
  chainfireConfigFile = tomlFormat.generate "chainfire.toml" (lib.recursiveUpdate generatedConfig cfg.settings);
in
{
  options.services.chainfire = {
    enable = lib.mkEnableOption "chainfire cluster coordination service";

    nodeId = lib.mkOption {
      type = lib.types.str;
      default = config.networking.hostName;
      description = "Unique node identifier for the Raft cluster";
    };

    port = lib.mkOption {
      type = lib.types.port;
      default = 2379;
      description = "Port for chainfire API";
    };

    raftPort = lib.mkOption {
      type = lib.types.port;
      default = 2380;
      description = "Port for chainfire Raft protocol";
    };

    raftAddr = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = null;
      description = "Full address for Raft (host:port). If null, uses 0.0.0.0:raftPort";
    };

    apiAddr = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = null;
      description = "Full address for API (host:port). If null, uses 0.0.0.0:port";
    };

    initialPeers = lib.mkOption {
      type = lib.types.listOf lib.types.str;
      default = [];
      description = "Initial Raft peers for cluster bootstrap (format: nodeId=addr:port)";
      example = [ "node01=10.0.0.1:2380" "node02=10.0.0.2:2380" ];
    };

    gossipPort = lib.mkOption {
      type = lib.types.port;
      default = 2381;
      description = "Port for chainfire gossip protocol";
    };

    httpPort = lib.mkOption {
      type = lib.types.port;
      default = 8081;
      description = "Port for chainfire HTTP/admin API (used for cluster join)";
    };

    gossipAddr = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = null;
      description = "Full gossip advertise/listen address (host:port). If null, derives from apiAddr host and gossipPort.";
    };

    clusterId = lib.mkOption {
      type = lib.types.int;
      default = 1;
      description = "Cluster identifier written into the ChainFire config.";
    };

    bootstrap = lib.mkOption {
      type = lib.types.bool;
      default = true;
      description = "Whether this node boots using the configured initial_members set.";
    };

    role = lib.mkOption {
      type = lib.types.enum [ "control_plane" "worker" ];
      default = "control_plane";
      description = "Logical node role advertised through ChainFire gossip metadata.";
    };

    raftRole = lib.mkOption {
      type = lib.types.enum [ "voter" "learner" "none" ];
      default = "voter";
      description = "Raft participation role written into the ChainFire config.";
    };

    dataDir = lib.mkOption {
      type = lib.types.path;
      default = "/var/lib/chainfire";
      description = "Data directory for chainfire";
    };

    settings = lib.mkOption {
      type = lib.types.attrs;
      default = {};
      description = "Additional configuration settings";
    };

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

  config = lib.mkIf cfg.enable {
    # Create system user
    users.users.chainfire = {
      isSystemUser = true;
      group = "chainfire";
      description = "Chainfire service user";
      home = cfg.dataDir;
    };

    users.groups.chainfire = {};

    # Create systemd service
    systemd.services.chainfire = {
      description = "Chainfire Distributed Cluster Coordination Service";
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ];

      serviceConfig = {
        Type = "simple";
        User = "chainfire";
        Group = "chainfire";
        Restart = "on-failure";
        RestartSec = "10s";

        # State directory management
        StateDirectory = "chainfire";
        StateDirectoryMode = "0750";

        # Security hardening
        NoNewPrivileges = true;
        PrivateTmp = true;
        ProtectSystem = "strict";
        ProtectHome = true;
        ReadWritePaths = [ cfg.dataDir ];

        ExecStart = "${cfg.package}/bin/chainfire --config ${chainfireConfigFile}";
      };
    };
  };
}
