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

with lib;

let

  cfg = config.profiles.clerie.dn42-router;

  myAsn = 4242422574;
  ospf6Table = 1337;
  bgp6Table = 2342;

in {

  options.profiles.clerie.dn42-router = {
    enable = mkEnableOption "DN42 router base config";
    loopbackIp = mkOption {
      type = types.str;
      description = "IPv6 lookback IP";
    };
    routerId = mkOption {
      type = types.str;
      description = "IPv6 lookback IP";
    };
    ospfInterfaces = mkOption {
      type = with types; listOf str;
      default = [];
    };
    ibgpPeers = mkOption {
      type = with types; listOf (submodule ({ ... }: {
        options = {
          remoteAddress = mkOption {
            type = types.str;
          };
          peerName = mkOption {
            type = types.str;
          };
        };
      }));
      default = [];
      description = "External bgp peers";
    };
    bgpPeers = mkOption {
      type = with types; listOf (submodule ({ ... }: {
        options = {
          localAddress = mkOption {
            type = types.str;
          };
          remoteAddress = mkOption {
            type = types.str;
          };
          peerName = mkOption {
            type = types.str;
          };
          remoteAsn = mkOption {
            type = types.str;
          };
        };
      }));
      default = [];
      description = "External bgp peers";
    };
    wireguardPeers = mkOption {
      type = with types; listOf (submodule ({ ... }: {
        options = {
          interfaceName = mkOption {
            type = types.str;
          };
          localAddress = mkOption {
            type = types.str;
          };
          #localAddressPrefixlen = ;
          remoteAddress = mkOption {
            type = types.str;
          };
          #remoteAddressPrefixlen = ;
          #localPrivateKey = ;
          #remotePublicKey = ;
          #localListenPort = ;
          #remoteEnpoint = ;
          peerName = mkOption {
            type = types.str;
          };
          remoteAsn = mkOption {
            type = types.str;
          };
        };
      }));
      default = [];
      description = "External bgp peers connected via wireguard";
    };
    birdExtraConfig = mkOption {
      type = types.str;
      default = "";
    };
  };

  config = mkIf config.profiles.clerie.dn42-router.enable {

    systemd.network.config.routeTables = {
      bgp6 = bgp6Table;
      ospf6 = ospf6Table;
    };
    systemd.network.config.addRouteTablesToIPRoute2 = true;

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

    boot.kernel.sysctl = {
      "net.ipv4.ip_forward" = true;
      "net.ipv6.conf.all.forwarding" = true;
    };

    networking.firewall.checkReversePath = false;

    # Open Firewall for BGP
    networking.firewall.allowedTCPPorts = [ 179 ];
    # Open Fireall for OSPF
    networking.firewall.extraCommands = ''
      ip6tables -A INPUT -p ospfigp -j ACCEPT
      iptables -A INPUT -p ospfigp -j ACCEPT
    '';

    systemd.network.netdevs."10-lo-dn42" = {
      netdevConfig = {
        Kind = "dummy";
        Name = "lo-dn42";
      };
    };

    systemd.network.networks."10-lo-dn42" = {
      matchConfig.Name = "lo-dn42";
      address = [ "${ cfg.loopbackIp }/128" ];
      linkConfig.RequiredForOnline = "no";
      routingPolicyRules = [
        {
          Priority = 10000;
          Family = "ipv6";
          To = "fd56:4902:eca0::/48";
          Table = "ospf6";
        }
        {
          Priority = 11000;
          Family = "ipv6";
          Table = "bgp6";
        }
        {
          Priority = 15000;
          Family = "ipv6";
          From = "fd56:4902:eca0::/48";
          Type = "unreachable";
        }
        {
          Priority = 16000;
          Family = "ipv6";
          To = "fd56:4902:eca0::/48";
          Type = "unreachable";
        }
        {
          Priority = 20000;
          Family = "both";
          Table = "main";
        }
      ];
    };

    services.bird.enable = true;
    services.bird.package = pkgs.bird2;
    services.bird.config = ''
      router id ${ cfg.routerId };
  
      ipv6 table ospf6;
      ipv6 table bgp6;
  
      protocol direct {
        interface "lo-dn42";
        ipv6 {
          table ospf6;
        };
      }
  
      protocol static {
        ipv6 {
          table bgp6;
        };
        route fd56:4902:eca0::/48 via "lo-dn42";
      }
  
      protocol kernel {
        ipv6 {
          table ospf6;
          export filter {
            krt_prefsrc=${ cfg.loopbackIp };
            accept;
          };
          import none;
        };
        kernel table ${ toString ospf6Table };
      }
  
      protocol kernel {
        ipv6 {
          table bgp6;
          export filter {
            krt_prefsrc=${ cfg.loopbackIp };
            accept;
          };
          import none;
        };
        kernel table ${ toString bgp6Table };
      }

      protocol device {
        scan time 10;
      }

      protocol ospf v3 {
        ipv6 {
          table ospf6;
          import all;
          export all;
        };
        area 0 {
          ${ concatMapStringsSep "\n" (interfaceName: ''
          interface "${interfaceName}" {
            cost 80;
            type broadcast;
          };
          '') cfg.ospfInterfaces}
        };
      }
    
      template bgp ibgp_peer {
        local as ${ toString myAsn };
        graceful restart on;
        source address ${ cfg.loopbackIp};
        ipv6 {
          table bgp6;
          igp table ospf6;
          next hop self;
          import keep filtered;
          import all;
          export all;
        };
      }
    
      ${concatMapStringsSep "\n" ( peerConfig: ''
      protocol bgp ${peerConfig.peerName} from ibgp_peer {
        neighbor ${peerConfig.remoteAddress} as ${ toString myAsn };
      }
      '') cfg.ibgpPeers}

      template bgp bgp_peer {
        local as ${ toString myAsn };
        graceful restart on;
        ipv6 {
          table bgp6;
          next hop self;
          import keep filtered;
          import filter {
            if net ~ [fd00::/8{48,64}] then accept;
            reject;
          };
          export filter {
            if net ~ [fd00::/8{48,64}] then accept;
            reject;
          };
        };
      }

      ${concatMapStringsSep "\n" ( peerConfig: ''
      protocol bgp ${peerConfig.peerName} from bgp_peer {
        neighbor ${peerConfig.remoteAddress} as ${peerConfig.remoteAsn};
        source address ${peerConfig.localAddress};
      }
      '') cfg.bgpPeers}

      ${concatMapStringsSep "\n" ( peerConfig: ''
      protocol bgp ${peerConfig.peerName} from bgp_peer {
        neighbor ${peerConfig.remoteAddress}%${peerConfig.interfaceName} as ${peerConfig.remoteAsn};
        source address ${peerConfig.localAddress};
      }
      '') cfg.wireguardPeers}

      ${ cfg.birdExtraConfig }

    '';
  
  };
}