# SPDX-FileCopyrightText: 2024 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0

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

let
  inherit (depot.third_party) hackyplayer;

  yamlFormat = pkgs.formats.yaml {};

  cfg = config.my.hackyplayer;
  processedConfig = lib.mapAttrs' (name: value: lib.nameValuePair "FLASK_${name}" (builtins.toJSON value)) cfg.config;
  readWritePaths = [
    cfg.config.VIDEO_OUTPUT
    cfg.config.VIDEO_TEMP
    cfg.config.LOG_DIR
  ]
  ++ (map (value: value.OUTPUT_DIR) cfg.config.WATCHFOLDERS)
  ++ (map (value: value.FULLPATH) cfg.config.WATCHFOLDERS);
in
{
  options.my.hackyplayer = {
    enable = lib.mkEnableOption "hackyplayer";
    hostname = lib.mkOption {
      type = lib.types.str;
    };
    listenAddresses = lib.mkOption {
      type = lib.types.nullOr (lib.types.listOf lib.types.str);
      default = null;
    };
    config = lib.mkOption {
      type = lib.types.anything;
      default = let
        paths = {
          input = "/store/emf/2024/video/input";
          source = "/store/emf/2024/video/source";
          output = "/store/emf/2024/video/output";
          temp = "/store/emf/2024/video/tmp";
          logs = "/store/emf/2024/video/logs";
        };
      in {
        VIDEO_SOURCES = [{
          DISKDIR = paths.source;
          WEBDIR = "source";
          EXT = [".mp4"];
          NAME = "Source";
        } {
          DISKDIR = paths.output;
          WEBDIR = "output";
          EXT = [".mp4"];
          NAME = "Output";
        }];
        VIDEO_OUTPUT = paths.output;
        VIDEO_TEMP = paths.temp;
        WATCHFOLDERS = [{
          NAME = "input";
          FULLPATH = paths.input;
          OUTPUT_DIR = paths.source;
        }];
        LOG_DIR = paths.logs;
      };
    };
    caddyPrefixExtraHandles = lib.mkOption {
      type = lib.types.lines;
      default = "";
    };
    caddyPrefixExtraConfig = lib.mkOption {
      type = lib.types.lines;
      default = "";
    };
    gunicornConcurrency = lib.mkOption {
      type = lib.types.int;
      default = 4;
    };
    workerConcurrency = lib.mkOption {
      type = lib.types.int;
      default = 4;
    };
    vouch = {
      enable = lib.mkEnableOption "hackyplayer-vouch";
      config = lib.mkOption {
        type = lib.types.submodule {
          freeformType = yamlFormat.type;
        };
        default = {};
      };
    };
  };

  config = lib.mkMerge [
    (lib.mkIf cfg.enable {
      users.users.hackyplayer = {
        isSystemUser = true;
        group = "hackyplayer";
      };
      users.groups.hackyplayer = {};

      systemd.services.hackyplayer = {
        serviceConfig = {
          User = "hackyplayer";
          ProtectSystem = "strict";
          ProtectHome = "read-only";
          PrivateTmp = true;
          RemoveIPC = true;
          RuntimeDirectory = "hackyplayer";
          ExecStart = "${hackyplayer}/bin/gunicorn -w ${toString cfg.gunicornConcurrency} --bind unix:/run/hackyplayer/hackyplayer.sock hackyplayer.app:app";
          EnvironmentFile = config.my.vault.secrets.hackyplayer-environment.path;
        };
        wantedBy = [ "hackyplayer.target" ];
        environment = processedConfig;
      };
      systemd.services.hackyplayer-celery = {
        serviceConfig = {
          User = "hackyplayer";
          ProtectSystem = "strict";
          ProtectHome = "read-only";
          PrivateTmp = true;
          RemoveIPC = true;
          RuntimeDirectory = "hackyplayer-celery";
          ExecStart = "${hackyplayer}/bin/celery -A hackyplayer.tasks worker --concurrency ${toString cfg.workerConcurrency} --loglevel INFO --statedb /run/hackyplayer-celery/state.db";
          ReadWritePaths = readWritePaths;
          EnvironmentFile = config.my.vault.secrets.hackyplayer-environment.path;
        };
        wantedBy = [ "hackyplayer.target" ];
        environment = processedConfig;
      };
      systemd.targets.hackyplayer = {
        wantedBy = [ "multi-user.target" ];
      };

      my.vault.secrets.hackyplayer-environment = {
        reloadOrRestartUnits = ["hackyplayer.service"];
        group = "root";
        template = ''
          {{ with secret "kv/apps/hackyplayer" }}
          {{ .Data.data.environment }}
          {{ end }}
        '';
      };

      environment.systemPackages = let
        envPrelude = lib.concatStringsSep "\n" (
          lib.mapAttrsToList (var: val: "export ${var}=${lib.escapeShellArg val}") processedConfig);
      in [
        (pkgs.writeShellApplication {
          name = "hackyplayer-celery";
          text = ''
            ${envPrelude}
            exec ${hackyplayer}/bin/celery -A hackyplayer.tasks "$@"
          '';
        })
        (pkgs.writeShellApplication {
          name = "hackyplayer-flask";
          text = ''
            ${envPrelude}
            exec ${hackyplayer}/bin/flask -A hackyplayer.app:app "$@"
          '';
        })
      ];

      services.caddy = {
        enable = true;
        virtualHosts."${cfg.hostname}" = {
          listenAddresses = lib.mkIf (cfg.listenAddresses != null) cfg.listenAddresses;
          extraConfig = ''
            ${cfg.caddyPrefixExtraHandles}
            handle {
              ${cfg.caddyPrefixExtraConfig}
              ${lib.concatMapStrings (src: ''
                route /static/video/${src.WEBDIR}/* {
                  uri strip_prefix /static/video/${src.WEBDIR}
                  root * ${src.DISKDIR}
                  file_server
                }
              '') cfg.config.VIDEO_SOURCES}
              route /static/* {
                uri strip_prefix /static
                root * ${hackyplayer}/${hackyplayer.python.sitePackages}/hackyplayer/static
                file_server
              }
              reverse_proxy unix//run/hackyplayer/hackyplayer.sock
            }
          '';
        };
      };
      systemd.services.caddy.serviceConfig.SupplementaryGroups = lib.mkAfter [ "hackyplayer" ];

      services.redis.servers."".enable = true;

      my.hackyplayer.vouch.enable = lib.mkDefault true;
      my.hackyplayer.vouch.config = lib.mkDefault {
        vouch = {
          allowAllUsers = true;
          cookie.domain = "voc.emf.camp";
          listen = "unix:/run/hacky-vouchproxy/sock";
          socket_mode = "0770";
          socket_group = "hackyplayer";
          document_root = "/_vouch";
        };

        oauth = let
          base = "https://identity.emfcamp.org";
        in {
          provider = "oidc";
          auth_url = "${base}/oauth2/authorize";
          token_url = "${base}/oauth2/token";
          user_info_url = "${base}/oauth2/userinfo";
          scopes = [ "profile" ];
          callback_url = "https://hackyplayer.voc.emf.camp/auth";
        };
      };
    }) (lib.mkIf cfg.vouch.enable {
      environment.etc."hacky-vouchproxy/config.yaml".source = yamlFormat.generate "hackyplayer-vouch-config.yaml" cfg.vouch.config;
      systemd.services.hacky-vouchproxy = {
        serviceConfig = {
          User = "hacky-vouchproxy";
          SupplementaryGroups = [ "hackyplayer" ];
          DynamicUser = true;
          RuntimeDirectory = "hacky-vouchproxy";
          ExecStart = "${pkgs.vouch-proxy}/bin/vouch-proxy -config /etc/hacky-vouchproxy/config.yaml";
          EnvironmentFile = config.my.vault.secrets.hacky-vouchproxy-environment.path;
        };
        wantedBy = [ "multi-user.target" ];
      };

      my.vault.secrets.hacky-vouchproxy-environment = {
        reloadOrRestartUnits = ["hacky-vouchproxy.service"];
        group = "root";
        template = ''
          {{ with secret "kv/apps/hacky-vouchproxy" }}
          {{ .Data.data.environment }}
          {{ end }}
        '';
      };

      my.hackyplayer.caddyPrefixExtraHandles = lib.mkBefore ''
        handle /auth {
          reverse_proxy unix//run/hacky-vouchproxy/sock {
            rewrite /_vouch/auth
          }
        }
        handle /_vouch/* {
          reverse_proxy unix//run/hacky-vouchproxy/sock
        }
      '';
      my.hackyplayer.caddyPrefixExtraConfig = lib.mkBefore ''
        reverse_proxy unix//run/hacky-vouchproxy/sock {
          method GET
          rewrite /_vouch/validate

          header_up Host {host}

          @good status 2xx
          handle_response @good {
            request_header X-Vouch-User {rp.header.X-Vouch-User}
          }

          @bad status 4xx
          handle_response @bad {
            header Location /_vouch/login?url=https://{host}{uri}&vouch-failcount={rp.header.X-Vouch-Failcount}&X-Vouch-Token={rp.header.X-Vouch-JWT}&error={rp.header.X-Vouch-Err}
            respond 303 {
              close
            }
          }
        }
      '';
    })
  ];
}