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

let
  cfg = config.local.services.byedpi;

  exe = lib.getExe cfg.package;

  baseArgs = lib.cli.toGNUCommandLineShell { } {
    inherit (cfg.settings) ip port;
    buf-size = cfg.settings.bufferSize;
    debug = cfg.settings.debugLevel;
    max-conn = cfg.settings.connectionLimit;
    tfo = cfg.settings.tcpFastOpen.enable;
    no-udp = !cfg.settings.udp.enable;
    no-domain = !cfg.settings.domain.enable;
  };

  groupArgs = lib.flip map cfg.groupSettings (gs:
    lib.concatStringsSep " " [
      (lib.cli.toGNUCommandLineShell { } (
        if gs.proto == [ ] && gs.hostsFile == null then { auto = gs.name; } else {
          proto = lib.optionalDrvAttr (gs.proto != [ ]) (lib.concatStringsSep "," gs.proto);
          hosts = lib.optionalDrvAttr (gs.hostsFile != null) gs.hostsFile;
        }
      ))
      (lib.cli.toGNUCommandLineShell { } {
        inherit (gs) ttl split disorder oob disoob fake tlsrec md5sig;
      })
    ]
  );

  cliArgs = lib.concatStringsSep " " ([ baseArgs ] ++ groupArgs);

  mkSplitOption =
    let
      splitType = with lib.types;
        let strOrInt = either str int;
        in nullOr (either strOrInt (listOf strOrInt));
    in
    description: lib.mkOption {
      inherit description;
      type = splitType;
      default = null;
    };
in
{
  options.local.services.byedpi = with lib; {
    enable = mkEnableOption "byedpi";
    package = mkPackageOption pkgs "byedpi" { };
    openFirewall = mkEnableOption "Whether to open the required firewall ports in the firewall.";
    enableProxy = mkEnableOption "Whether to enable systemwide networking proxy";
    settings = {
      ip = mkOption {
        type = types.str;
        description = "Listening IP";
        default = "0.0.0.0";
      };
      port = mkOption {
        type = types.ints.u16;
        description = "Listening port";
        default = 1080;
      };
      bufferSize = mkOption {
        type = types.int;
        description = "Buffer size";
        default = 16384;
      };
      debugLevel = mkOption {
        type = types.ints.between 0 2;
        default = 0;
      };
      connectionLimit = mkOption {
        type = types.int;
        description = "Connection count limit";
        default = 512;
      };
      domain.enable = mkEnableOption "Enable domain resolving" // { default = true; };
      udp.enable = mkEnableOption "Enable UDP association" // { default = true; };
      tcpFastOpen.enable = mkEnableOption "Enable TCP Fast Open";
    };
    groupSettings = lib.mkOption {
      type = types.listOf (types.submodule ({ config, ... }: {
        options = {
          enable = mkEnableOption "Enable configs for hosts";
          name = mkOption {
            type = types.str;
          };
          hostsFile = mkOption {
            type = types.nullOr types.package;
            internal = true;
            readOnly = true;
          };
          hosts = mkOption {
            type = types.lines;
            default = "";
          };
          proto = mkOption {
            type = types.listOf (types.enum [ "tls" "http" "udp" "ipv4" ]);
            default = [ ];
          };
          ttl = mkOption {
            type = types.nullOr types.int;
            default = null;
          };
          split = mkSplitOption "Split packet at n";
          disorder = mkSplitOption "Split and send reverse order";
          oob = mkSplitOption "Split and send as OOB data";
          disoob = mkSplitOption "Split and send reverse order as OOB data";
          fake = mkSplitOption "Split and send fake packet";
          tlsrec = mkSplitOption "Make TLS record at position";
          md5sig = mkEnableOption "Add MD5 Signature option for fake packets";
        };
        config = {
          hostsFile = if config.hosts == "" then null else pkgs.writeText config.name config.hosts;
        };
      }));
    };
  };

  config = lib.mkIf cfg.enable {
    users.groups.byedpi = { };
    users.users.byedpi = {
      isSystemUser = true;
      group = "byedpi";
    };

    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.settings.port ];

    networking.proxy = lib.mkIf cfg.enableProxy rec {
      /*
        allProxy = "socks5://${cfg.settings.ip}:${toString cfg.settings.port}";
        httpProxy = allProxy;
        httpsProxy = allProxy;
      */
    };

    systemd.services.byedpi = {
      description = "Byedpi (Bypass DPI)";

      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ];

      path = [ cfg.package ];

      serviceConfig = {
        Type = "simple";
        User = "byedpi";
        Group = "byedpi";
        ExecStart = "${exe} ${cliArgs}";
      };
    };
  };
}