{ config, lib, pkgs, ... }:
let
  inherit (config.age) secrets;
  inherit (builtins) toString;
in
{
  imports = [
    ./gitea.nix
    ./authelia.nix
  ];
  config = {

    age.secrets.cloudflaredns = {
      file = ../../secrets/cloudflare-dns.age;
      group = "secrets";
    };

    age.secrets.htpasswd-cam = {
      file = ../../secrets/htpasswd-cam.age;
      group = "nginx";
      mode = "0750";
    };
    age.secrets.htpasswd = {
      file = ../../secrets/htpasswd.age;
      group = "nginx";
      mode = "0750";
    };

    age.secrets.authelia-users = {
      file = ../../secrets/authelia-users.age;
      group = "authelia-shared";
      mode = "0750";
    };

    age.secrets.authelia-jwt = {
      file = ../../secrets/authelia-jwt.age;
      group = "authelia-shared";
      mode = "0750";
    };

    age.secrets.authelia-storage = {
      file = ../../secrets/authelia-storage.age;
      group = "authelia-shared";
      mode = "0750";
    };

    age.secrets.authelia-session = {
      file = ../../secrets/authelia-session.age;
      group = "authelia-shared";
      mode = "0750";
    };

    age.secrets.anki = {
      file = ../../secrets/anki-user.age;
    };

    age.secrets.homepage = {
      file = ../../secrets/homepage.age;
    };

    age.secrets.paperless-admin = {
      file = ../../secrets/paperless-admin.age;
    };

    users.groups.secrets = {};
    users.users.acme.extraGroups = [ "secrets" ];

    security.acme = {
      acceptTerms = true;
      maxConcurrentRenewals = 1;

      defaults.email = "iancoguz@gmail.com";

      certs = {
        "protogen.io" = {
          credentialFiles = {
            "CLOUDFLARE_EMAIL_FILE" = pkgs.writeText "email" "iancoguz@gmail.com";
            "CLOUDFLARE_API_KEY_FILE" = config.age.secrets.cloudflaredns.path;
          };

          dnsProvider = "cloudflare";
          domain = "protogen.io";
          extraDomainNames = [
            "*.protogen.io"
            "nullbite.com"
            "*.nullbite.com"
            "nullbite.dev"
            "*.nullbite.dev"
            "nbt.sh"
            "*.nbt.sh"
            "proot.link"
            "*.proot.link"
            "nullbite.xyz"
            "*.nullbite.xyz"
          ];
        };
      };
    };

    users.users.nginx.extraGroups = [ "acme" ];

    networking.firewall.allowedTCPPorts = [
      80 443
      # this is needed for node to work for some reason
      8123
    ];

    users.groups.authelia-shared = { };
    services.authelia.instances = lib.mapAttrs (inst: opts: {
      enable = true;
      group = "authelia-shared";
      secrets = {
        jwtSecretFile = config.age.secrets.authelia-jwt.path;
        storageEncryptionKeyFile = config.age.secrets.authelia-storage.path;
        sessionSecretFile = config.age.secrets.authelia-session.path;
      };
      settings = {
        access_control.default_policy = "one_factor";
        storage.local.path = "/var/lib/authelia-${inst}/db.sqlite";
        session.cookies = [
          {
            domain = "protogen.io";
            authelia_url = "https://auth.protogen.io";
            default_redirection_url = "https://searx.protogen.io";
          }
          {
            domain = "nbt.sh";
            authelia_url = "https://auth.nbt.sh";
            default_redirection_url = "https://admin.nbt.sh";
          }
          {
            domain = "proot.link";
            authelia_url = "https://auth.proot.link";
            default_redirection_url = "https://admin.proot.link";
          }
        ];
        session.redis = {
          host = config.services.redis.servers.authelia.unixSocket;
        };
        notifier.filesystem.filename = "/var/lib/authelia-${inst}/notification.txt";
        authentication_backend.file.path = config.age.secrets.authelia-users.path;
        server.port = lib.mkIf (opts ? port) (opts.port or null);
        theme = "auto";
      };
    }) {
      main = {
        domain = "protogen.io";
        # port = 9091 # default
      };
    };

    services.redis = {
      servers.authelia = {
        enable = true;
      };
    };

    users.users."${config.services.authelia.instances.main.user}".extraGroups = let
      name = config.services.redis.servers.authelia.user;
    in [ name ];

    services.nginx = {
      enable = true;
      recommendedProxySettings = true;
      recommendedTlsSettings = true;
      recommendedOptimisation = true;

      commonHttpConfig = ''
        port_in_redirect off;
      '';

      virtualHosts = let
        useACMEHost = "protogen.io";
        mkProxy = args@{ upstream ? "http://127.0.0.1:${builtins.toString args.port}", auth ? false, authelia ? false, extraConfig ? {}, ... }:
        lib.mkMerge [
          {
            inherit useACMEHost;
            forceSSL = true;
            locations."/" = {
              proxyPass = upstream;
              proxyWebsockets = true;
            };
          }
          (lib.mkIf auth {
            basicAuthFile = config.age.secrets.htpasswd.path;
          })
          (lib.mkIf authelia {
            authelia.instance = lib.mkDefault "main";
          })
          extraConfig
        ];

        # mkReverseProxy = port: {
        #   inherit useACMEHost;
        #   forceSSL = true;
        #   locations."/" = {
        #     proxyPass = "http://127.0.0.1:${builtins.toString port}";
        #     proxyWebsockets = true;
        #   };
        # };

        mkAuthProxy = port: mkProxy { inherit port; authelia = true; };

        mkReverseProxy = port: mkProxy { inherit port; };
      in (lib.mapAttrs (domain: instance: { forceSSL = true; inherit useACMEHost; authelia.endpoint = { inherit instance; };}) {
        "auth.protogen.io" = "main";
        "auth.nbt.sh" = "main";
        "auth.proot.link" = "main";
      }) // {
        "changedetection.protogen.io" = mkReverseProxy 5000;

        # firefly
        "firefly.protogen.io" = mkReverseProxy 8083;
        "firefly-import.protogen.io" = mkAuthProxy 8084;

        "gitea.protogen.io" = mkReverseProxy 3000;

        # home assistant
        "hass.protogen.io" = mkReverseProxy 8123;
        "node.protogen.io" = mkReverseProxy 1880;
        "z2m.protogen.io" = mkAuthProxy 8124;
        "vsc-hass.protogen.io" = mkReverseProxy 1881;

        # jellyfin
        "room.protogen.io" = mkReverseProxy 8096;
        "deemix.protogen.io" = mkAuthProxy 6595;

        # libreddit auth 8087
        "libreddit.protogen.io" = {
          locations."/".return = "302 https://redlib.protogen.io$request_uri";
          forceSSL = true;
          useACMEHost = "protogen.io";
        };
        "redlib.protogen.io" = mkAuthProxy 8087;
        "rss.protogen.io" = mkReverseProxy 8082;
        "blahaj.protogen.io" = mkReverseProxy 8086;
        "paper.protogen.io" = mkReverseProxy config.services.paperless.port;

        # octoprint (proxy_addr is 10.10.1.8)
        "print.protogen.io" = lib.mkMerge [ (mkProxy { authelia = true; upstream = "http://10.10.1.8:80"; })
        {
          locations."/webcam" = {
            proxyPass = "http://10.10.1.8:80$request_uri";
            proxyWebsockets = true;
            basicAuthFile = config.age.secrets.htpasswd-cam.path;
            authelia.method = null;
          };
        }];

        # searx auth 8088 (none for /favicon.ico, /autocompleter, /opensearch.xml)
        "search.protogen.io".locations."/".return = "302 https://searx.protogen.io$request_uri";
        "searx.protogen.io" = let
          port = 8088;
        in mkProxy { authelia = true; inherit port; extraConfig = {
          locations = lib.genAttrs [ "/favicon.ico" "/autocompleter" "/opensearch.xml" ] (attr: {
            proxyPass = "http://localhost:${builtins.toString port}";
            proxyWebsockets = true;
            authelia.method = null;
            extraConfig = ''
              auth_basic off;
            '';
          });
        };};

        # URL shortener
        "nbt.sh" = mkProxy { port = 8090; extraConfig.serverAliases = [ "proot.link" ]; };
        "admin.nbt.sh" = mkProxy { authelia = true; port = 8091; extraConfig.serverAliases = [ "admin.proot.link" ]; };

        # uptime
        "uptime.protogen.io" = mkReverseProxy 3001;
        "kuma.protogen.io".locations."/".return = "301 https://uptime.protogen.io";

        "anki.protogen.io" = mkReverseProxy config.services.anki-sync-server.port;

        # homepage
        "home.protogen.io" = mkAuthProxy 8089;

        "lounge.protogen.io" = mkAuthProxy 9000;

        "trackmap.protogen.io" = let
          root = pkgs.modpacks.notlite-ctm-static;
        in {
          useACMEHost = "protogen.io";
          forceSSL = true;
          authelia.instance = "main";
          locations."/" = {
            inherit root;
            extraConfig = ''
              autoindex off;
            '';
          };
          locations."/api/" = {
            proxyPass = "http://10.10.0.3:3876";
            proxyWebsockets = true;
            extraConfig = ''
              chunked_transfer_encoding off;
              proxy_buffering off;
              proxy_cache off;
            '';
          };
        };

        # main site
        "protogen.io" = {
          serverAliases = [ "x.protogen.io" ];
          useACMEHost = "protogen.io";
          forceSSL = true;
          locations."/" = {
            root = "/srv/http";
            extraConfig = ''
              autoindex on;
            '';
          };
        };

        # fallback for known hosts
        "nullbite.com" = {
          forceSSL = true;
          useACMEHost = "protogen.io";
          locations."/" = {
            return = "302 https://protogen.io$request_uri";
          };
          serverAliases = [ "www.nullbite.com" "nullbite.dev" "www.nullbite.dev" "www.protogen.io" "nullbite.xyz" "www.nullbite.xyz" ];
        };

        # show blank page for unknown hosts
        "localhost" = {
          default = true;
          addSSL = true;
          useACMEHost = "protogen.io";
          locations."/" = {
            return = "404";
          };
        };


      };
    };

    # https://gethomepage.dev
    services.homepage-dashboard = let
      entry = name: value: { "${name}" = value; };
      makeBookmark = name: {...}@attrs: entry name [ attrs ];
      makeBookmark' = name: icon: abbr: href: makeBookmark name ({ inherit abbr href; } // lib.optionalAttrs (icon != null) { inherit icon; });
    in {
      enable = true;
      listenPort = 8089;
      environmentFile = secrets.homepage.path;

      # bookmarks customCSS customJS docker environmentFile kubernetes services settings widgets
      settings = {
        theme = "dark";
        color = "slate";
      };

      widgets = [
        (entry "resources" {
          cpu = true;
          memory = true;
          disk = "/";
        })
        (entry "search" {
          provider = "custom";
          target = "_self";
          url = "https://searx.protogen.io/search?q=";
          suggestionUrl = "https://searx.protogen.io/autocompleter?q=";
          showSearchSuggestions = true;
        })
      ];

      services = let
        service = name: subdomain: icon: {...}@attrs: entry name ({
          href = "https://${subdomain}.protogen.io";
          inherit icon;
        } // attrs);
        basicService = name: subdomain: icon: service name subdomain icon {};
      in [
        (entry "unsorted" [
          (basicService "Firefly III" "firefly" "firefly-iii")
          (basicService "Gitea" "gitea" "gitea")
          (basicService "Home Assistant" "hass" "home-assistant")
          (basicService "Node-RED" "node" "node-red")
          (basicService "Zigbee2MQTT" "z2m" "zigbee2mqtt")
          (basicService "Code Server (Home Assistant)" "vsc-hass" "vscode")
          (basicService "Deemix" "deemix" "deemix")
          (basicService "Miniflux" "rss" "miniflux")
          (basicService "mlmym" "blahaj" "lemmy-light")
          (basicService "Octoprint" "print" "octoprint")
          (basicService "SearXNG" "searx" "searxng")
          (basicService "TheLounge" "lounge" "thelounge")
          (basicService "Paperless" "paper" "paperless-ngx")
          (entry "Shlink" { href = "https://admin.nbt.sh"; icon = "shlink"; })
          (basicService "Create Track Map" "trackmap" "")

          ((x: service x x x {
            widget = {
            };
          }) "changedetection")

          (service "Uptime Kuma" "uptime" "uptime-kuma" {
            widget = {
              type = "uptimekuma";
              url = "https://uptime.protogen.io";
              slug = "all";
            };
          })
          (service "Jellyfin" "room" "jellyfin" {
            widget = {
              type = "jellyfin";
              url = "https://room.protogen.io";
              key = "{{HOMEPAGE_VAR_JELLYFIN}}";
              enableBlocks = true;
              enableNowPlaying = true;
              enableUser = true;
            };
          })
        ])
      ];

      bookmarks = [
        (entry "Admin" [
          (makeBookmark' "Backblaze" "backblaze" "BZ" "https://secure.backblaze.com/user_signin.htm")
          (makeBookmark' "Cloudflare" "cloudflare" "CF" "https://dash.cloudflare.com")
          (makeBookmark' "Porkbun" "porkbun" "PB" "https://porkbun.com/account/domainsSpeedy")
          (makeBookmark' "Namecheap" "namechea" "NC" "https://ap.www.namecheap.com/")
        ])
        (entry "Developer" [
          (makeBookmark' "GitHub" "github" "GH" "https://github.com")
        ])
        (entry "Local" [
          (makeBookmark' "Syncthing" "syncthing" "ST" "http://127.0.0.1:8384")
          (makeBookmark' "Iris" null "IR" "http://localhost:6680/iris/")
        ])
        (entry "Entertainment" [
          (makeBookmark' "Redlib" "redlib" "RL" "https://redlib.protogen.io")
        ])
      ];
    };

    virtualisation.docker = {
      enable = true;
      storageDriver = "btrfs";
    };

    # needed for mDNS in Home Assistant
    networking.firewall.allowedUDPPorts = [ 5353 ];

    systemd.services.redlib.environment = {
      REDLIB_DEFAULT_SUBSCRIPTIONS = lib.pipe ./reddit-subscriptions.txt [
        builtins.readFile
        (lib.splitString "\n")
        (lib.filter (x: x != ""))
        (lib.concatStringsSep "+")
      ];
      REDLIB_DEFAULT_SHOW_NSFW = "on";
      REDLIB_DEFAULT_BLUR_NSFW = "on";
      REDLIB_DEFAULT_BLUR_SPOILER = "on";
      REDLIB_DEFAULT_USE_HLS = "on";
      REDLIB_DEFAULT_DISABLE_VISIT_REDDIT_CONFIRMATION = "on";

      REDLIB_ENABLE_RSS = "on";
    };

    services.redlib = {
      enable = true;
      port = 8087;
    };

    services.thelounge = {
      enable = true;
      extraConfig = {
        prefetch = true;
        prefetchStorage = true;
        prefetchMaxImageSize = 8192;
      };
    };

    services.redis.servers.paperless.enable = true;
    services.paperless = {
      enable = true;
      # default is "localhost", binding should not rely on DNS (even if
      # localhost is hard-coded 99.999% of the time)
      address = "127.0.0.1";
      passwordFile = secrets.paperless-admin.path;
      settings = {
        PAPERLESS_ADMIN_USER = "nullbite";
        PAPERLESS_REDIS = "unix://${config.services.redis.servers.paperless.unixSocket}";
        PAPERLESS_URL = "https://paper.protogen.io";
        PAPERLESS_TIKA_ENABLED = true;
        PAPERLESS_TIKA_ENDPOINT = "http://localhost:${toString config.services.tika.port}";
        PAPERLESS_TIKA_GOTENBERG_ENDPOINT =
          "http://localhost:${toString config.services.gotenberg.port}";
      };
    };
    users.users."${config.services.paperless.user}".extraGroups = let
      name = config.services.redis.servers.paperless.group;
    in [ name ];

    services.gotenberg = {
      enable = true;
      port = 3002;
    };
    services.tika.enable = true;

    services.anki-sync-server = {
      enable = true;
      address = "127.0.0.1";
      users = [
        {
          username = "nullbite";
          passwordFile = config.age.secrets.anki.path;
        }
      ];
    };
  };
}