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

with lib;

let
  cfg = config.profiles.clerie.wg-clerie;
in

{
  options = {
    profiles.clerie.wg-clerie = {
      enable = mkEnableOption "VPN for public static IP";
      privateKeyFile = mkOption {
        type = with types; nullOr str;
        default = null;
        description = "Path to file containing private key for wireguard interface";
      };
      ipv6s = mkOption {
        type = with types; listOf str;
        default = [];
        description = "IPv6 interface addresses";
      };
      ipv4s = mkOption {
        type = with types; listOf str;
        default = [];
        description = "IPv4 interface addresses";
      };
      defaultViaVPN = mkOption {
        type = types.bool;
        default = true;
        description = "Use VPN default route for a protocol, if that protocol is unavailable in the underlay";
      };
    };
  };

  config = mkIf cfg.enable {

    systemd.network.config.routeTables = {
      wg-clerie = 200;
    };
    systemd.network.config.addRouteTablesToIPRoute2 = true;

    sops = (mkIf (cfg.privateKeyFile == null) {
      secrets.wg-clerie = {
        owner = "systemd-network";
        group = "systemd-network";
      };
    });

    networking.networkmanager.unmanaged = [
      "interface-name:wg-clerie"
    ];

    systemd.network.netdevs."10-wg-clerie" = {
      netdevConfig = {
        Kind = "wireguard";
        Name = "wg-clerie";
      };
      wireguardConfig = {
        PrivateKeyFile = if cfg.privateKeyFile != null then cfg.privateKeyFile else
          config.sops.secrets.wg-clerie.path;
        RouteTable = "wg-clerie";
      };
      wireguardPeers = [
        {
          PublicKey = "2p1Jqs3bkXbXHFWE6vp1yxHIFoUaZQEARS2nJzbkuBA=";
          AllowedIPs = [ "0.0.0.0/0" "::/0" "10.20.30.0/24" "2a01:4f8:c0c:15f1::/113" ];
          PersistentKeepalive = 25;
        }
      ];
    };

    systemd.network.networks."10-wg-clerie" = {
      matchConfig.Name = "wg-clerie";
      address = cfg.ipv6s ++ cfg.ipv4s;
      linkConfig.RequiredForOnline = "no";
      routingPolicyRules = (builtins.map
        (ip: {
          Priority = 19000;
          Family = "ipv6";
          From = ip;
          #Type = "table";
          Table = "wg-clerie";
        })
        cfg.ipv6s
      ) ++ (builtins.map
        (ip: {
          Priority = 19001;
          Family = "ipv6";
          From = ip;
          Type = "unreachable";
        })
        cfg.ipv6s
      ) ++ (builtins.map
        (ip: {
          Priority = 19000;
          Family = "ipv4";
          From = ip;
          #Type = "table";
          Table = "wg-clerie";
        })
        cfg.ipv4s
      ) ++ (builtins.map
        (ip: {
          Priority = 19001;
          Family = "ipv4";
          From = ip;
          Type = "unreachable";
        })
        cfg.ipv4s
      ) ++ [
        {
          Priority = 20000;
          Family = "ipv6";
          To = "2a01:4f8:c0c:15f1::1/128";
          IPProtocol = "udp";
          DestinationPort = 51820;
          #Type = "table";
          Table = "main";
        }
        {
          Priority = 20001;
          Family = "ipv6";
          To = "2a01:4f8:c0c:15f1::1/128";
          IPProtocol = "udp";
          DestinationPort = 51820;
          Type = "unreachable";
        }
        {
          Priority = 20000;
          Family = "ipv4";
          To = "78.47.183.82/32";
          IPProtocol = "udp";
          DestinationPort = 51820;
          #Type = "table";
          Table = "main";
        }
        {
          Priority = 20001;
          Family = "ipv4";
          To = "78.47.183.82/32";
          IPProtocol = "udp";
          DestinationPort = 51820;
          Type = "unreachable";
        }
        {
          Priority = 21000;
          Family = "both";
          #Type = "table";
          Table = "main";
        }
      ] ++ (if cfg.defaultViaVPN then [
        {
          Priority = 21001;
          Family = "both";
          #Type = "table";
          Table = "wg-clerie";
        }
      ] else []) ++ [
        {
          Priority = 22000;
          Family = "both";
          Type = "unreachable";
        }
      ];
    };

    systemd.services."wg-clerie-endpoint-refresh" = {
      serviceConfig = {
        Type = "oneshot";
      };

      path = [ pkgs.wireguard-tools pkgs.iproute2 ];

      script = ''
        set -euo pipefail

        # Don't do anything as long as interface is not configured
        if ! wg show wg-clerie endpoints > /dev/null; then
          exit 0
        fi

        endpoint=""

        if ip route get 2a01:4f8:c0c:15f1::1 ipproto udp dport 51820 &>/dev/null; then
          endpoint="[2a01:4f8:c0c:15f1::1]:51820"
        else
          endpoint="78.47.183.82:51820"
        fi

        wg set wg-clerie peer "2p1Jqs3bkXbXHFWE6vp1yxHIFoUaZQEARS2nJzbkuBA=" endpoint "''${endpoint}"
      '';

      requires = [ "network-online.target" ];
      after = [ "network-online.target" ];
    };

    systemd.timers."wg-clerie-endpoint-refresh" = {
      wantedBy = [ "timers.target" ];

      timerConfig = {
        OnCalendar = "*-*-* *:*:0/5";
        RandomizedDelaySec = "5s";
      };

      requires = [ "network-online.target" ];
      after = [ "network-online.target" ];
    };

    environment.systemPackages = [ pkgs.wireguard-tools ];

  };
}