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

let
  cfg = config.shb.arr;

  contracts = pkgs.callPackage ../contracts {};
  shblib = pkgs.callPackage ../../lib {};

  apps = {
    radarr = {
      settingsFormat = shblib.formatXML { enclosingRoot = "Config"; };
      moreOptions = {
        settings = lib.mkOption {
          description = "Specific options for radarr.";
          default = {};
          type = lib.types.submodule {
            freeformType = apps.radarr.settingsFormat.type;
            options = {
              ApiKey = lib.mkOption {
                type = shblib.secretFileType;
                description = "Path to api key secret file.";
              };
              LogLevel = lib.mkOption {
                type = lib.types.enum ["debug" "info"];
                description = "Log level.";
                default = "info";
              };
              Port = lib.mkOption {
                type = lib.types.port;
                description = "Port on which radarr listens to incoming requests.";
                default = 7878;
              };
              AnalyticsEnabled = lib.mkOption {
                type = lib.types.bool;
                description = "Wether to send anonymous data or not.";
                default = false;
              };
              BindAddress = lib.mkOption {
                type = lib.types.str;
                internal = true;
                default = "127.0.0.1";
              };
              UrlBase = lib.mkOption {
                type = lib.types.str;
                internal = true;
                default = "";
              };
              EnableSsl = lib.mkOption {
                type = lib.types.bool;
                internal = true;
                default = false;
              };
              AuthenticationMethod = lib.mkOption {
                type = lib.types.str;
                internal = true;
                default = "External";
              };
              AuthenticationRequired = lib.mkOption {
                type = lib.types.str;
                internal = true;
                default = "Enabled";
              };
            };
          };
        };
      };
    };
    sonarr = {
      settingsFormat = shblib.formatXML { enclosingRoot = "Config"; };
      moreOptions = {
        settings = lib.mkOption {
          description = "Specific options for sonarr.";
          default = {};
          type = lib.types.submodule {
            freeformType = apps.sonarr.settingsFormat.type;
            options = {
              ApiKey = lib.mkOption {
                type = shblib.secretFileType;
                description = "Path to api key secret file.";
              };
              LogLevel = lib.mkOption {
                type = lib.types.enum ["debug" "info"];
                description = "Log level.";
                default = "info";
              };
              Port = lib.mkOption {
                type = lib.types.port;
                description = "Port on which sonarr listens to incoming requests.";
                default = 8989;
              };
              BindAddress = lib.mkOption {
                type = lib.types.str;
                internal = true;
                default = "127.0.0.1";
              };
              UrlBase = lib.mkOption {
                type = lib.types.str;
                internal = true;
                default = "";
              };
              EnableSsl = lib.mkOption {
                type = lib.types.bool;
                internal = true;
                default = false;
              };
              AuthenticationMethod = lib.mkOption {
                type = lib.types.str;
                internal = true;
                default = "External";
              };
              AuthenticationRequired = lib.mkOption {
                type = lib.types.str;
                internal = true;
                default = "Enabled";
              };
            };
          };
        };
      };
    };
    bazarr = {
      settingsFormat = shblib.formatXML { enclosingRoot = "Config"; };
      moreOptions = {
        settings = lib.mkOption {
          description = "Specific options for bazarr.";
          default = {};
          type = lib.types.submodule {
            freeformType = apps.bazarr.settingsFormat.type;
            options = {
              LogLevel = lib.mkOption {
                type = lib.types.enum ["debug" "info"];
                description = "Log level.";
                default = "info";
              };
              Port = lib.mkOption {
                type = lib.types.port;
                description = "Port on which bazarr listens to incoming requests.";
                default = 6767;
                readOnly = true;
              };
            };
          };
        };
      };
    };
    readarr = {
      settingsFormat = shblib.formatXML { enclosingRoot = "Config"; };
      moreOptions = {
        settings = lib.mkOption {
          description = "Specific options for readarr.";
          default = {};
          type = lib.types.submodule {
            freeformType = apps.readarr.settingsFormat.type;
            options = {
              LogLevel = lib.mkOption {
                type = lib.types.enum ["debug" "info"];
                description = "Log level.";
                default = "info";
              };
              Port = lib.mkOption {
                type = lib.types.port;
                description = "Port on which readarr listens to incoming requests.";
                default = 8787;
              };
            };
          };
        };
      };
    };
    lidarr = {
      settingsFormat = shblib.formatXML { enclosingRoot = "Config"; };
      moreOptions = {
        settings = lib.mkOption {
          description = "Specific options for lidarr.";
          default = {};
          type = lib.types.submodule {
            freeformType = apps.lidarr.settingsFormat.type;
            options = {
              LogLevel = lib.mkOption {
                type = lib.types.enum ["debug" "info"];
                description = "Log level.";
                default = "info";
              };
              Port = lib.mkOption {
                type = lib.types.port;
                description = "Port on which lidarr listens to incoming requests.";
                default = 8686;
              };
            };
          };
        };
      };
    };
    jackett = {
      settingsFormat = pkgs.formats.json {};
      moreOptions = {
        settings = lib.mkOption {
          description = "Specific options for jackett.";
          default = {};
          type = lib.types.submodule {
            freeformType = apps.jackett.settingsFormat.type;
            options = {
              ApiKey = lib.mkOption {
                type = shblib.secretFileType;
                description = "Path to api key secret file.";
              };
              FlareSolverrUrl = lib.mkOption {
                type = lib.types.nullOr lib.types.str;
                description = "FlareSolverr endpoint.";
                default = null;
              };
              OmdbApiKey = lib.mkOption {
                type = lib.types.nullOr shblib.secretFileType;
                description = "File containing the Open Movie Database API key.";
                default = null;
              };
              ProxyType = lib.mkOption {
                type = lib.types.enum [ "-1" "0" "1" "2" ];
                default = "-1";
                description = ''
                -1 = disabled
                0 = HTTP
                1 = SOCKS4
                2 = SOCKS5
                '';
              };
              ProxyUrl = lib.mkOption {
                type = lib.types.nullOr lib.types.str;
                description = "URL of the proxy. Ignored if ProxyType is set to -1";
                default = null;
              };
              ProxyPort = lib.mkOption {
                type = lib.types.nullOr lib.types.port;
                description = "Port of the proxy. Ignored if ProxyType is set to -1";
                default = null;
              };
              Port = lib.mkOption {
                type = lib.types.port;
                description = "Port on which jackett listens to incoming requests.";
                default = 9117;
                readOnly = true;
              };
              AllowExternal = lib.mkOption {
                type = lib.types.bool;
                internal = true;
                default = false;
              };
              UpdateDisabled = lib.mkOption {
                type = lib.types.bool;
                internal = true;
                default = true;
              };
            };
          };
        };
      };
    };
  };

  vhosts = { extraBypassResources ? [] }: c: {
    inherit (c) subdomain domain authEndpoint ssl;

    upstream = "http://127.0.0.1:${toString c.settings.Port}";
    autheliaRules = lib.optionals (!(isNull c.authEndpoint)) [
      {
        domain = "${c.subdomain}.${c.domain}";
        policy = "bypass";
        resources = extraBypassResources ++ [
          "^/api.*"
        ];
      }
      {
        domain = "${c.subdomain}.${c.domain}";
        policy = "two_factor";
        subject = ["group:arr_user"];
      }
    ];
  };

  appOption = name: c: lib.nameValuePair name (lib.mkOption {
    description = "Configuration for ${name}";
    default = {};
    type = lib.types.submodule {
      options = {
        enable = lib.mkEnableOption name;

        subdomain = lib.mkOption {
          type = lib.types.str;
          description = "Subdomain under which ${name} will be served.";
          example = name;
        };

        domain = lib.mkOption {
          type = lib.types.str;
          description = "Domain under which ${name} will be served.";
          example = "example.com";
        };

        dataDir = lib.mkOption {
          type = lib.types.str;
          description = "Directory where ${name} stores data.";
          default = "/var/lib/${name}";
        };

        ssl = lib.mkOption {
          description = "Path to SSL files";
          type = lib.types.nullOr contracts.ssl.certs;
          default = null;
        };

        authEndpoint = lib.mkOption {
          type = lib.types.nullOr lib.types.str;
          default = null;
          description = "Endpoint to the SSO provider. Leave null to not have SSO configured.";
          example = "https://authelia.example.com";
        };

        backup = lib.mkOption {
          type = contracts.backup;
          description = ''
            Backup configuration. This is an output option.

            Use it to initialize a block implementing the "backup" contract.
            For example, with the restic block:

            ```
            shb.restic.instances."${name}" = {
              enable = true;

              # Options specific to Restic.
            } // config.shb.${name}.backup;
            ```
          '';
          readOnly = true;
          default = {
            user = name;
            sourceDirectories = [
              cfg.${name}.dataDir
            ];
            excludePatterns = [".db-shm" ".db-wal" ".mono"];
          };
        };
      } // (c.moreOptions or {});
    };
  });
in
{
  options.shb.arr = lib.listToAttrs (lib.mapAttrsToList appOption apps);

  config = lib.mkMerge [
    (lib.mkIf cfg.radarr.enable (
    let
      cfg' = cfg.radarr;
      isSSOEnabled = !(isNull cfg'.authEndpoint);
    in
    {
      services.nginx.enable = true;

      services.radarr = {
        enable = true;
        dataDir = "/var/lib/radarr";
      };

      systemd.services.radarr.preStart = shblib.replaceSecrets {
        userConfig = cfg'.settings
                     // (lib.optionalAttrs isSSOEnabled {
                       AuthenticationRequired = "DisabledForLocalAddresses";
                       AuthenticationMethod = "External";
                     });
        resultPath = "${config.services.radarr.dataDir}/config.xml";
        generator = shblib.replaceSecretsFormatAdapter apps.radarr.settingsFormat;
      };

      shb.nginx.vhosts = [ (vhosts {} cfg') ];
    }))

    (lib.mkIf cfg.sonarr.enable (
    let
      cfg' = cfg.sonarr;
      isSSOEnabled = !(isNull cfg'.authEndpoint);
    in
    {
      services.nginx.enable = true;

      services.sonarr = {
        enable = true;
        dataDir = "/var/lib/sonarr";
      };
      users.users.sonarr = {
        extraGroups = [ "media" ];
      };

      systemd.services.sonarr.preStart = shblib.replaceSecrets {
        userConfig = cfg'.settings
                     // (lib.optionalAttrs isSSOEnabled {
                       AuthenticationRequired = "DisabledForLocalAddresses";
                       AuthenticationMethod = "External";
                     });
        resultPath = "${config.services.sonarr.dataDir}/config.xml";
        generator = apps.sonarr.settingsFormat.generate;
      };

      shb.nginx.vhosts = [ (vhosts {} cfg') ];
    }))

    (lib.mkIf cfg.bazarr.enable (
    let
      cfg' = cfg.bazarr;
      isSSOEnabled = !(isNull cfg'.authEndpoint);
    in
    {
      services.bazarr = {
        enable = true;
        listenPort = cfg'.settings.Port;
      };
      users.users.bazarr = {
        extraGroups = [ "media" ];
      };
      systemd.services.bazarr.preStart = shblib.replaceSecrets {
        userConfig = cfg'.settings
                     // (lib.optionalAttrs isSSOEnabled {
                       AuthenticationRequired = "DisabledForLocalAddresses";
                       AuthenticationMethod = "External";
                     });
        resultPath = "/var/lib/bazarr/config.xml";
        generator = apps.bazarr.settingsFormat.generate;
      };

      shb.nginx.vhosts = [ (vhosts {} cfg') ];
    }))

    (lib.mkIf cfg.readarr.enable (
    let
      cfg' = cfg.readarr;
    in
    {
      services.readarr = {
        enable = true;
        dataDir = "/var/lib/readarr";
      };
      users.users.readarr = {
        extraGroups = [ "media" ];
      };
      systemd.services.readarr.preStart = shblib.replaceSecrets {
        userConfig = cfg'.settings;
        resultPath = "${config.services.readarr.dataDir}/config.xml";
        generator = apps.readarr.settingsFormat.generate;
      };

      shb.nginx.vhosts = [ (vhosts {} cfg') ];
    }))

    (lib.mkIf cfg.lidarr.enable (
    let
      cfg' = cfg.lidarr;
      isSSOEnabled = !(isNull cfg'.authEndpoint);
    in
    {
      services.lidarr = {
        enable = true;
        dataDir = "/var/lib/lidarr";
      };
      users.users.lidarr = {
        extraGroups = [ "media" ];
      };
      systemd.services.lidarr.preStart = shblib.replaceSecrets {
        userConfig = cfg'.settings
                     // (lib.optionalAttrs isSSOEnabled {
                       AuthenticationRequired = "DisabledForLocalAddresses";
                       AuthenticationMethod = "External";
                     });
        resultPath = "${config.services.lidarr.dataDir}/config.xml";
        generator = apps.lidarr.settingsFormat.generate;
      };

      shb.nginx.vhosts = [ (vhosts {} cfg') ];
    }))

    (lib.mkIf cfg.jackett.enable (
    let
      cfg' = cfg.jackett;
    in
    {
      services.jackett = {
        enable = true;
        dataDir = "/var/lib/jackett";
      };
      # TODO: avoid implicitly relying on the media group
      users.users.jackett = {
        extraGroups = [ "media" ];
      };
      systemd.services.jackett.preStart = shblib.replaceSecrets {
        userConfig = shblib.renameAttrName cfg'.settings "ApiKey" "APIKey";
        resultPath = "${config.services.jackett.dataDir}/ServerConfig.json";
        generator = apps.jackett.settingsFormat.generate;
      };

      shb.nginx.vhosts = [ (vhosts {
        extraBypassResources = [ "^/dl.*" ];
      } cfg') ];
    }))
  ];
}
