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

let
  cfg    = config.services.voip;
  phones = import ./phones.nix { inherit lib; };

  inherit (phones) models;
  allPhones       = phones.mkAllPhones cfg;
  intercomEntries = phones.mkIntercomEntries cfg allPhones;

  mohDirs      = import ./asterisk/moh.nix      { inherit lib pkgs cfg; };
  greetingDirs = import ./asterisk/greetings.nix { inherit lib pkgs cfg; };
  confFiles    = import ./asterisk/default.nix   { inherit lib cfg models allPhones intercomEntries mohDirs greetingDirs; };

  directory        = import ./provisioning/directory.nix { inherit lib pkgs cfg allPhones intercomEntries; };
  provisioningRoot = import ./provisioning/default.nix  { inherit lib pkgs cfg models allPhones; };

  diagram = import ./dashboard.nix { inherit lib pkgs cfg models allPhones intercomEntries; };

  # True when any *File option is set — Asterisk's execincludes=yes is required in that case.
  hasRuntimeSecrets =
    lib.any (t: t.hostFile != null || t.usernameFile != null || t.passwordFile != null || t.callerIdFile != null)
      (lib.attrValues cfg.sipTrunks)
    || lib.any (d: d.numberFile != null) (lib.attrValues cfg.dids);

  # Nginx Lua handler: reads the static HTML template and substitutes every
  # @@/path/to/keyfile@@ marker with the file's first line at request time.
  luaPageHandler = pkgs.writeText "voip-page.lua" ''
    local f = assert(io.open("${diagram.webRoot}/index.html", "rb"))
    local html = f:read("*a")
    f:close()
    -- Placeholders embed the full key file path: @@/var/lib/voip-keys/name@@
    html = html:gsub("@@([^@]+)@@", function(path)
      local kf = io.open(path, "r")
      if not kf then return "<em>(not yet uploaded)</em>" end
      local val = kf:read("*l")
      kf:close()
      return val or ""
    end)
    ngx.header.content_type = "text/html; charset=utf-8"
    ngx.print(html)
  '';

in {
  imports = [ ./options.nix ];

  config = lib.mkIf cfg.enable {

    services.voip.ntpServer = lib.mkDefault cfg.serverAddress;

    assertions = import ./assertions.nix { inherit lib cfg models allPhones; };

    services.asterisk = {
      enable      = true;
      confFiles   = confFiles;
      # execincludes=yes is required when any *File option is in use.
      extraConfig = lib.optionalString hasRuntimeSecrets ''
        [options]
        execincludes=yes
      '';
    };

    services.atftpd = {
      enable       = true;
      root         = "${provisioningRoot}";
      extraOptions = [ "--verbose=7" ];
    };

    services.nginx = {
      enable  = true;
      # OpenResty bundles nginx + LuaJIT + resty.core and all required libraries.
      # Needed for request-time secret substitution in the status page.
      package = lib.mkIf hasRuntimeSecrets (lib.mkDefault pkgs.openresty);
      # Cisco phones fetch provisioning files (SEP*.cnf.xml, dialplan-*.xml,
      # backgrounds) over TFTP (primary) and HTTP port 6970 (fallback).
      # Both serve the same Nix-built provisioning root.
      virtualHosts."voip-provisioning" = {
        listen = [{ addr = "0.0.0.0"; port = 6970; }];
        locations."/" = {
          root        = "${provisioningRoot}";
          extraConfig = "autoindex off;";
        };
      };
      virtualHosts."voip-directory" = {
        listen = [{ addr = "0.0.0.0"; port = cfg.directoryPort; }];
        locations = {
          "= /directory.xml"      = { alias = "${directory.menuFile}";     extraConfig = "default_type text/xml;"; };
          "= /directory-list.xml" = { alias = "${directory.listFile}";     extraConfig = "default_type text/xml;"; };
          "= /intercom.xml"       = { alias = "${directory.intercomFile}"; extraConfig = "default_type text/xml;"; };
          "/" = {
            root      = "${diagram.webRoot}";
            extraConfig = lib.optionalString (!hasRuntimeSecrets) "index index.html;";
          };
        } // lib.optionalAttrs hasRuntimeSecrets {
          # Exact-match the index so the Lua handler intercepts it before the
          # prefix location /. Other assets (voip.dot, SVG) fall through to /.
          "= /"           = { extraConfig = "default_type text/html;\ncontent_by_lua_file ${luaPageHandler};"; };
          "= /index.html" = { extraConfig = "default_type text/html;\ncontent_by_lua_file ${luaPageHandler};"; };
        };
      };
    };

    # voip-keys group: both asterisk (#exec reads) and nginx (Lua reads) need access.
    # Key files must be deployed with group = "voip-keys" and permissions = "0640".
    users.groups.voip-keys = {};
    users.users.asterisk.extraGroups = [ "voip-keys" ];
    users.users.nginx.extraGroups    = [ "voip-keys" ];

    systemd.tmpfiles.rules = [ "d /var/log/nginx 0750 nginx nginx -" ];

    networking.firewall = {
      allowedTCPPorts      = [ cfg.sipPort cfg.directoryPort 6970 ];
      allowedUDPPorts      = [ cfg.sipPort 69 ];
      allowedUDPPortRanges = [{ from = cfg.rtpStart; to = cfg.rtpEnd; }];
    };
  };
}
