{
  lib,
  pkgs,
  config,
  ...
}:
let
  inherit (lib)
    mkOption
    types
    mkIf
    optionalString
    ;
  cfg = config.services.opengfw;
in
{
  options.services.opengfw = {
    enable = lib.mkEnableOption ''
      OpenGFW, A flexible, easy-to-use, open source implementation of GFW on Linux
    '';

    package = lib.mkPackageOption pkgs "opengfw" { default = "opengfw"; };

    user = mkOption {
      default = "opengfw";
      type = types.singleLineStr;
      description = "Username of the OpenGFW user.";
    };

    dir = mkOption {
      default = "/var/lib/opengfw";
      type = types.singleLineStr;
      description = ''
        Working directory of the OpenGFW service and home of `opengfw.user`.
      '';
    };

    logFile = mkOption {
      default = null;
      type = types.nullOr types.path;
      example = "/var/lib/opengfw/opengfw.log";
      description = ''
        File to write the output to instead of systemd.
      '';
    };

    logFormat = mkOption {
      description = ''
        Format of the logs. [logFormatMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L62)
      '';
      default = "json";
      example = "console";
      type = types.enum [
        "json"
        "console"
      ];
    };

    pcapReplay = mkOption {
      default = null;
      example = "./opengfw.pcap";
      type = types.nullOr types.path;
      description = ''
        Path to PCAP replay file.
        In pcap mode, none of the actions in the rules have any effect.
        This mode is mainly for debugging.
      '';
    };

    logLevel = mkOption {
      description = ''
        Level of the logs. [logLevelMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L55)
      '';
      default = "info";
      example = "warn";
      type = types.enum [
        "debug"
        "info"
        "warn"
        "error"
      ];
    };

    rulesFile = mkOption {
      default = null;
      type = types.nullOr types.path;
      description = ''
        Path to file containing OpenGFW rules.
      '';
    };

    settingsFile = mkOption {
      default = null;
      type = types.nullOr types.path;
      description = ''
        Path to file containing OpenGFW settings.
      '';
    };

    settings = mkOption {
      default = null;
      description = ''
        Settings passed to OpenGFW. [Example config](https://gfw.dev/docs/build-run/#config-example)
      '';
      type = types.nullOr (
        types.submodule {
          options = {
            replay = mkOption {
              description = ''
                PCAP replay settings.
              '';
              default = { };
              type = types.submodule {
                options = {
                  realtime = mkOption {
                    description = ''
                      Whether the packets in the PCAP file should be replayed in "real time" (instead of as fast as possible).
                    '';
                    default = false;
                    example = true;
                    type = types.bool;
                  };
                };
              };
            };

            io = mkOption {
              description = ''
                IO settings.
              '';
              default = { };
              type = types.submodule {
                options = {
                  queueSize = mkOption {
                    description = "IO queue size.";
                    type = types.int;
                    default = 1024;
                    example = 2048;
                  };
                  local = mkOption {
                    description = ''
                      Set to false if you want to run OpenGFW on FORWARD chain. (e.g. on a router)
                    '';
                    type = types.bool;
                    default = true;
                    example = false;
                  };
                  rst = mkOption {
                    description = ''
                      Set to true if you want to send RST for blocked TCP connections, needs `local = false`.
                    '';
                    type = types.bool;
                    default = !cfg.settings.io.local;
                    defaultText = "`!config.services.opengfw.settings.io.local`";
                    example = false;
                  };
                  rcvBuf = mkOption {
                    description = "Netlink receive buffer size.";
                    type = types.int;
                    default = 4194304;
                    example = 2097152;
                  };
                  sndBuf = mkOption {
                    description = "Netlink send buffer size.";
                    type = types.int;
                    default = 4194304;
                    example = 2097152;
                  };
                };
              };
            };
            ruleset = mkOption {
              description = ''
                The path to load specific local geoip/geosite db files.
                If not set, they will be automatically downloaded from (Loyalsoldier/v2ray-rules-dat)[https://github.com/Loyalsoldier/v2ray-rules-dat].
              '';
              default = { };
              type = types.submodule {
                options = {
                  geoip = mkOption {
                    description = "Path to `geoip.dat`.";
                    default = null;
                    type = types.nullOr types.path;
                  };
                  geosite = mkOption {
                    description = "Path to `geosite.dat`.";
                    default = null;
                    type = types.nullOr types.path;
                  };
                };
              };
            };
            workers = mkOption {
              default = { };
              description = "Worker settings.";
              type = types.submodule {
                options = {
                  count = mkOption {
                    type = types.int;
                    description = ''
                      Number of workers.
                      Recommended to be no more than the number of CPU cores
                    '';
                    default = 4;
                    example = 8;
                  };
                  queueSize = mkOption {
                    type = types.int;
                    description = "Worker queue size.";
                    default = 16;
                    example = 32;
                  };
                  tcpMaxBufferedPagesTotal = mkOption {
                    type = types.int;
                    description = ''
                      TCP max total buffered pages.
                    '';
                    default = 4096;
                    example = 8192;
                  };
                  tcpMaxBufferedPagesPerConn = mkOption {
                    type = types.int;
                    description = ''
                      TCP max total bufferd pages per connection.
                    '';
                    default = 64;
                    example = 128;
                  };
                  tcpTimeout = mkOption {
                    type = types.str;
                    description = ''
                      How long a connection is considered dead when no data is being transferred.
                      Dead connections are purged from TCP reassembly pools once per minute.
                    '';
                    default = "10m";
                    example = "5m";
                  };
                  udpMaxStreams = mkOption {
                    type = types.int;
                    description = "UDP max streams.";
                    default = 4096;
                    example = 8192;
                  };
                };
              };
            };
          };
        }
      );
    };

    rules = mkOption {
      default = [ ];
      description = ''
        Rules passed to OpenGFW. [Example rules](https://gfw.dev/docs/rules)
      '';
      type = types.listOf (
        types.submodule {
          options = {
            name = mkOption {
              description = "Name of the rule.";
              example = "block google dns";
              type = types.singleLineStr;
            };

            action = mkOption {
              description = ''
                Action of the rule. [Supported actions](https://gfw.dev/docs/rules#supported-actions)
              '';
              default = "allow";
              example = "block";
              type = types.enum [
                "allow"
                "block"
                "drop"
                "modify"
              ];
            };

            log = mkOption {
              description = "Whether to enable logging for the rule.";
              default = true;
              example = false;
              type = types.bool;
            };

            expr = mkOption {
              description = ''
                [Expr Language](https://expr-lang.org/docs/language-definition) expression using [analyzers](https://gfw.dev/docs/analyzers) and [functions](https://gfw.dev/docs/functions).
              '';
              type = types.str;
              example = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "google.com"})'';
            };

            modifier = mkOption {
              default = null;
              description = ''
                Modification of specified packets when using the `modify` action. [Available modifiers](https://github.com/apernet/OpenGFW/tree/master/modifier)
              '';
              type = types.nullOr (
                types.submodule {
                  options = {
                    name = mkOption {
                      description = "Name of the modifier.";
                      type = types.singleLineStr;
                      example = "dns";
                    };

                    args = mkOption {
                      description = "Arguments passed to the modifier.";
                      type = types.attrs;
                      example = {
                        a = "0.0.0.0";
                        aaaa = "::";
                      };
                    };
                  };
                }
              );
            };
          };
        }
      );

      example = [
        {
          name = "block v2ex http";
          action = "block";
          expr = ''string(http?.req?.headers?.host) endsWith "v2ex.com"'';
        }
        {
          name = "block google socks";
          action = "block";
          expr = ''string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80'';
        }
        {
          name = "v2ex dns poisoning";
          action = "modify";
          modifier = {
            name = "dns";
            args = {
              a = "0.0.0.0";
              aaaa = "::";
            };
          };
          expr = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})'';
        }
      ];
    };
  };

  config =
    let
      format = pkgs.formats.yaml { };

      settings =
        if cfg.settings != null then
          format.generate "opengfw-config.yaml" cfg.settings
        else
          cfg.settingsFile;
      rules = if cfg.rules != [ ] then format.generate "opengfw-rules.yaml" cfg.rules else cfg.rulesFile;
    in
    mkIf cfg.enable {
      security.wrappers.OpenGFW = {
        owner = cfg.user;
        group = cfg.user;
        capabilities = "cap_net_admin+ep";
        source = "${cfg.package}/bin/OpenGFW";
      };

      systemd.services.opengfw = {
        description = "OpenGFW";
        wantedBy = [ "multi-user.target" ];
        after = [ "network.target" ];
        path = with pkgs; [ iptables ];

        preStart = ''
          ${optionalString (rules != null) "ln -sf ${rules} rules.yaml"}
          ${optionalString (settings != null) "ln -sf ${settings} config.yaml"}
        '';

        script = ''
          ${config.security.wrapperDir}/OpenGFW \
            -f ${cfg.logFormat} \
            -l ${cfg.logLevel} \
            ${optionalString (cfg.pcapReplay != null) "-p ${cfg.pcapReplay}"} \
            -c config.yaml \
            rules.yaml
        '';

        serviceConfig = rec {
          WorkingDirectory = cfg.dir;
          ExecReload = "kill -HUP $MAINPID";
          Restart = "always";
          User = cfg.user;
          StandardOutput = mkIf (cfg.logFile != null) "append:${cfg.logFile}";
          StandardError = StandardOutput;
        };
      };

      users = {
        groups.${cfg.user} = { };
        users.${cfg.user} = {
          description = "opengfw user";
          isSystemUser = true;
          group = cfg.user;
          home = cfg.dir;
          createHome = true;
          homeMode = "750";
        };
      };
    };
  meta.maintainers = with lib.maintainers; [ eum3l ];
}
