{ config, lib, options, pkgs, utils, ... }:
with lib;
let
  cfg = config.services.unifi-video;
  opt = options.services.unifi-video;
  mainClass = "com.ubnt.airvision.Main";
  cmd = ''
    ${pkgs.jsvc}/bin/jsvc \
    -cwd ${stateDir} \
    -debug \
    -verbose:class \
    -nodetach \
    -user unifi-video \
    -home ${cfg.jrePackage}/lib/openjdk \
    -cp ${pkgs.commonsDaemon}/share/java/commons-daemon-1.2.4.jar:${stateDir}/lib/airvision.jar \
    -pidfile ${cfg.pidFile} \
    -procname unifi-video \
    -Djava.security.egd=file:/dev/./urandom \
    -Xmx${toString cfg.maximumJavaHeapSize}M \
    -Xss512K \
    -XX:+UseG1GC \
    -XX:+UseStringDeduplication \
    -XX:MaxMetaspaceSize=768M \
    -Djava.library.path=${stateDir}/lib \
    -Djava.awt.headless=true \
    -Djavax.net.ssl.trustStore=${stateDir}/etc/ufv-truststore \
    -Dfile.encoding=UTF-8 \
    -Dav.tempdir=/var/cache/unifi-video
  '';

  mongoConf = pkgs.writeTextFile {
    name = "mongo.conf";
    executable = false;
    text = ''
      # for documentation of all options, see https://www.mongodb.com/docs/manual/reference/configuration-options/

      storage:
         dbPath: ${cfg.dataDir}/db
         journal:
            enabled: true
         syncPeriodSecs: 60

      systemLog:
         destination: file
         logAppend: true
         path: ${stateDir}/logs/mongod.log

      net:
         port: 7441
         bindIp: 127.0.0.1
         http:
            enabled: false

      operationProfiling:
         slowOpThresholdMs: 500
         mode: off
    '';
  };


  mongoWtConf = pkgs.writeTextFile {
    name = "mongowt.conf";
    executable = false;
    text = ''
      # for documentation of all options, see:
      #   https://www.mongodb.com/docs/manual/reference/configuration-options/

      storage:
         dbPath: ${cfg.dataDir}/db-wt
         journal:
            enabled: true
         wiredTiger:
            engineConfig:
               cacheSizeGB: 1

      systemLog:
         destination: file
         logAppend: true
         path: logs/mongod.log

      net:
         port: 7441
         bindIp: 127.0.0.1

      operationProfiling:
         slowOpThresholdMs: 500
         mode: off
    '';
  };

  stateDir = "/var/lib/unifi-video";

in
{

  options.services.unifi-video = {

    enable = mkOption {
      type = types.bool;
      default = false;
      description = ''
        Whether or not to enable the unifi-video service.
      '';
    };

    jrePackage = mkPackageOption pkgs "jre8" { };

    unifiVideoPackage = mkPackageOption pkgs "unifi-video" { };

    mongodbPackage = mkPackageOption pkgs "mongodb" {
      default = "mongodb-5_0";
    };

    logDir = mkOption {
      type = types.str;
      default = "${stateDir}/logs";
      description = ''
        Where to store the logs.
      '';
    };

    dataDir = mkOption {
      type = types.str;
      default = "${stateDir}/data";
      description = ''
        Where to store the database and other data.
      '';
    };

    openFirewall = mkOption {
      type = types.bool;
      default = false;
      description = ''
        Whether or not to open the required ports on the firewall.
      '';
    };

    maximumJavaHeapSize = mkOption {
      type = types.nullOr types.int;
      default = 1024;
      example = 4096;
      description = ''
        Set the maximum heap size for the JVM in MB.
      '';
    };

    pidFile = mkOption {
      type = types.path;
      default = "${cfg.dataDir}/unifi-video.pid";
      defaultText = literalExpression ''"''${config.${opt.dataDir}}/unifi-video.pid"'';
      description = "Location of unifi-video pid file.";
    };

  };

  config = mkIf cfg.enable {

    warnings = optional
      (options.services.unifi-video.openFirewall.highestPrio >= (mkOptionDefault null).priority)
      "The current services.unifi-video.openFirewall = true default is deprecated and will change to false in 22.11. Set it explicitly to silence this warning.";

    users.users.unifi-video = {
      description = "UniFi Video controller daemon user";
      home = stateDir;
      group = "unifi-video";
      isSystemUser = true;
    };
    users.groups.unifi-video = {};

    networking.firewall = mkIf cfg.openFirewall {
      # https://help.ui.com/hc/en-us/articles/217875218-UniFi-Video-Ports-Used
      allowedTCPPorts = [
        7080 # HTTP portal
        7443 # HTTPS portal
        7445 # Video over HTTP (mobile app)
        7446 # Video over HTTPS (mobile app)
        7447 # RTSP via the controller
        7442 # Camera management from cameras to NVR over WAN
      ];
      allowedUDPPorts = [
        6666 # Inbound camera streams sent over WAN
      ];
    };

    systemd.tmpfiles.rules = [
      "d '${stateDir}' 0700 unifi-video unifi-video - -"
      "d '/var/cache/unifi-video' 0700 unifi-video unifi-video - -"

      "d '${stateDir}/logs' 0700 unifi-video unifi-video - -"
      "C '${stateDir}/etc' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/etc"
      "C '${stateDir}/webapps' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/webapps"
      "C '${stateDir}/email' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/email"
      "C '${stateDir}/fw' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/fw"
      "C '${stateDir}/lib' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/lib"

      "d '${stateDir}/data' 0700 unifi-video unifi-video - -"
      "d '${stateDir}/data/db' 0700 unifi-video unifi-video - -"
      "C '${stateDir}/data/system.properties' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/etc/system.properties"

      "d '${stateDir}/bin' 0700 unifi-video unifi-video - -"
      "f '${stateDir}/bin/evostreamms' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/evostreamms"
      "f '${stateDir}/bin/libavcodec.so.54' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/libavcodec.so.54"
      "f '${stateDir}/bin/libavformat.so.54' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/libavformat.so.54"
      "f '${stateDir}/bin/libavutil.so.52' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/libavutil.so.52"
      "f '${stateDir}/bin/ubnt.avtool' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/ubnt.avtool"
      "f '${stateDir}/bin/ubnt.updater' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/bin/ubnt.updater"
      "C '${stateDir}/bin/mongo' 0700 unifi-video unifi-video - ${cfg.mongodbPackage}/bin/mongo"
      "C '${stateDir}/bin/mongod' 0700 unifi-video unifi-video - ${cfg.mongodbPackage}/bin/mongod"
      "C '${stateDir}/bin/mongoperf' 0700 unifi-video unifi-video - ${cfg.mongodbPackage}/bin/mongoperf"
      "C '${stateDir}/bin/mongos' 0700 unifi-video unifi-video - ${cfg.mongodbPackage}/bin/mongos"

      "d '${stateDir}/conf' 0700 unifi-video unifi-video - -"
      "C '${stateDir}/conf/evostream' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/evostream"
      "Z '${stateDir}/conf/evostream' 0700 unifi-video unifi-video - -"
      "L+ '${stateDir}/conf/mongodv3.0+.conf' 0700 unifi-video unifi-video - ${mongoConf}"
      "L+ '${stateDir}/conf/mongodv3.6+.conf' 0700 unifi-video unifi-video - ${mongoConf}"
      "L+ '${stateDir}/conf/mongod-wt.conf' 0700 unifi-video unifi-video - ${mongoWtConf}"
      "L+ '${stateDir}/conf/catalina.policy' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/catalina.policy"
      "L+ '${stateDir}/conf/catalina.properties' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/catalina.properties"
      "L+ '${stateDir}/conf/context.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/context.xml"
      "L+ '${stateDir}/conf/logging.properties' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/logging.properties"
      "L+ '${stateDir}/conf/server.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/server.xml"
      "L+ '${stateDir}/conf/tomcat-users.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/tomcat-users.xml"
      "L+ '${stateDir}/conf/web.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/web.xml"
    ];

    systemd.services.unifi-video = {
      description = "UniFi Video NVR daemon";
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ] ;
      unitConfig.RequiresMountsFor = stateDir;
      # Make sure package upgrades trigger a service restart
      restartTriggers = [ cfg.unifiVideoPackage cfg.mongodbPackage ];
      path = with pkgs; [ gawk coreutils busybox which jre8 lsb-release libcap util-linux ];
      serviceConfig = {
        Type = "simple";
        ExecStart = "${(removeSuffix "\n" cmd)} ${mainClass} start";
        ExecStop = "${(removeSuffix "\n" cmd)} stop ${mainClass} stop";
        Restart = "on-failure";
        UMask = "0077";
        User = "unifi-video";
        WorkingDirectory = "${stateDir}";
      };
    };
  };

  imports = [
    (mkRenamedOptionModule [ "services" "unifi-video" "openPorts" ] [ "services" "unifi-video" "openFirewall" ])
  ];

  meta.maintainers = with lib.maintainers; [ rsynnest ];
}
