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

let
  cfg = config.services.apigateway;
  tomlFormat = pkgs.formats.toml {};
  authProviderType = lib.types.submodule {
    options = {
      name = lib.mkOption {
        type = lib.types.str;
        description = "Auth provider name";
      };

      providerType = lib.mkOption {
        type = lib.types.str;
        default = "grpc";
        description = "Auth provider type (grpc)";
      };

      endpoint = lib.mkOption {
        type = lib.types.str;
        description = "Auth provider endpoint (e.g., http://127.0.0.1:9000)";
      };

      timeoutMs = lib.mkOption {
        type = lib.types.nullOr lib.types.int;
        default = null;
        description = "Auth provider timeout in milliseconds";
      };
    };
  };
  creditProviderType = lib.types.submodule {
    options = {
      name = lib.mkOption {
        type = lib.types.str;
        description = "Credit provider name";
      };

      providerType = lib.mkOption {
        type = lib.types.str;
        default = "grpc";
        description = "Credit provider type (grpc)";
      };

      endpoint = lib.mkOption {
        type = lib.types.str;
        description = "Credit provider endpoint (e.g., http://127.0.0.1:9100)";
      };

      timeoutMs = lib.mkOption {
        type = lib.types.nullOr lib.types.int;
        default = null;
        description = "Credit provider timeout in milliseconds";
      };
    };
  };
  routeAuthType = lib.types.submodule {
    options = {
      provider = lib.mkOption {
        type = lib.types.str;
        description = "Auth provider name to use";
      };

      mode = lib.mkOption {
        type = lib.types.enum [ "disabled" "optional" "required" ];
        default = "required";
        description = "Auth enforcement mode";
      };

      failOpen = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Allow requests on auth failures";
      };
    };
  };
  routeCreditType = lib.types.submodule {
    options = {
      provider = lib.mkOption {
        type = lib.types.str;
        description = "Credit provider name to use";
      };

      mode = lib.mkOption {
        type = lib.types.enum [ "disabled" "optional" "required" ];
        default = "required";
        description = "Credit enforcement mode";
      };

      units = lib.mkOption {
        type = lib.types.int;
        default = 1;
        description = "Credit units to reserve per request";
      };

      failOpen = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Allow requests on credit failures";
      };

      commitOn = lib.mkOption {
        type = lib.types.enum [ "success" "always" "never" ];
        default = "success";
        description = "Credit commit policy";
      };

      attributes = lib.mkOption {
        type = lib.types.attrsOf lib.types.str;
        default = {};
        description = "Extra credit attributes";
      };
    };
  };
  routeType = lib.types.submodule {
    options = {
      name = lib.mkOption {
        type = lib.types.str;
        description = "Route name";
      };

      pathPrefix = lib.mkOption {
        type = lib.types.str;
        description = "Path prefix to match";
      };

      upstream = lib.mkOption {
        type = lib.types.str;
        description = "Upstream base URL";
      };

      stripPrefix = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Strip the path prefix before proxying";
      };

      timeoutMs = lib.mkOption {
        type = lib.types.nullOr lib.types.int;
        default = null;
        description = "Per-route upstream timeout in milliseconds";
      };

      auth = lib.mkOption {
        type = lib.types.nullOr routeAuthType;
        default = null;
        description = "Auth configuration for this route";
      };

      credit = lib.mkOption {
        type = lib.types.nullOr routeCreditType;
        default = null;
        description = "Credit configuration for this route";
      };
    };
  };
  baseConfig = {
    http_addr = "0.0.0.0:${toString cfg.port}";
    log_level = "info";
  };
  toAuthProvider = provider: {
    name = provider.name;
    type = provider.providerType;
    endpoint = provider.endpoint;
  } // lib.optionalAttrs (provider.timeoutMs != null) {
    timeout_ms = provider.timeoutMs;
  };
  toCreditProvider = provider: {
    name = provider.name;
    type = provider.providerType;
    endpoint = provider.endpoint;
  } // lib.optionalAttrs (provider.timeoutMs != null) {
    timeout_ms = provider.timeoutMs;
  };
  toRouteAuth = auth: {
    provider = auth.provider;
    mode = auth.mode;
    fail_open = auth.failOpen;
  };
  toRouteCredit = credit: {
    provider = credit.provider;
    mode = credit.mode;
    units = credit.units;
    fail_open = credit.failOpen;
    commit_on = credit.commitOn;
    attributes = credit.attributes;
  };
  toRoute = route: {
    name = route.name;
    path_prefix = route.pathPrefix;
    upstream = route.upstream;
    strip_prefix = route.stripPrefix;
  }
  // lib.optionalAttrs (route.timeoutMs != null) {
    timeout_ms = route.timeoutMs;
  }
  // lib.optionalAttrs (route.auth != null) {
    auth = toRouteAuth route.auth;
  }
  // lib.optionalAttrs (route.credit != null) {
    credit = toRouteCredit route.credit;
  };
  generatedConfig = {
    max_body_bytes = cfg.maxBodyBytes;
    auth_providers = map toAuthProvider cfg.authProviders;
    credit_providers = map toCreditProvider cfg.creditProviders;
    routes = map toRoute cfg.routes;
  };
  configFile = tomlFormat.generate "apigateway.toml" (
    lib.recursiveUpdate baseConfig (lib.recursiveUpdate generatedConfig cfg.settings)
  );
  configPath = "/etc/apigateway/apigateway.toml";
in {
  options.services.apigateway = {
    enable = lib.mkEnableOption "apigateway service";

    port = lib.mkOption {
      type = lib.types.port;
      default = 8080;
      description = "Port for the API gateway HTTP listener";
    };

    iamAddr = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = null;
      description = "IAM service endpoint (host:port) to auto-configure IAM auth provider";
    };

    dataDir = lib.mkOption {
      type = lib.types.path;
      default = "/var/lib/apigateway";
      description = "Data directory for apigateway";
    };

    maxBodyBytes = lib.mkOption {
      type = lib.types.int;
      default = 16 * 1024 * 1024;
      description = "Maximum request body size in bytes";
    };

    authProviders = lib.mkOption {
      type = lib.types.listOf authProviderType;
      default = [];
      description = "Auth provider definitions";
    };

    creditProviders = lib.mkOption {
      type = lib.types.listOf creditProviderType;
      default = [];
      description = "Credit provider definitions";
    };

    routes = lib.mkOption {
      type = lib.types.listOf routeType;
      default = [];
      description = "API gateway routes";
    };

    settings = lib.mkOption {
      type = lib.types.attrs;
      default = {};
      description = "Additional API gateway TOML settings (merged into apigateway.toml)";
    };

    package = lib.mkOption {
      type = lib.types.package;
      default = pkgs.apigateway-server or (throw "apigateway-server package not found");
      description = "Package to use for apigateway";
    };
  };

  config = lib.mkIf cfg.enable {
    services.apigateway.authProviders = lib.mkIf (cfg.iamAddr != null) [
      {
        name = "iam";
        providerType = "grpc";
        endpoint = "http://${cfg.iamAddr}";
      }
    ];

    users.users.apigateway = {
      isSystemUser = true;
      group = "apigateway";
      description = "API gateway service user";
      home = cfg.dataDir;
    };

    users.groups.apigateway = {};

    systemd.services.apigateway = {
      description = "UltraCloud API Gateway";
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ];

      serviceConfig = {
        Type = "simple";
        User = "apigateway";
        Group = "apigateway";
        Restart = "on-failure";
        RestartSec = "10s";

        StateDirectory = "apigateway";
        StateDirectoryMode = "0750";

        NoNewPrivileges = true;
        PrivateTmp = true;
        ProtectSystem = "strict";
        ProtectHome = true;
        ReadWritePaths = [ cfg.dataDir ];

        ExecStart = "${cfg.package}/bin/apigateway-server --config ${configPath}";
      };
    };

    environment.etc."apigateway/apigateway.toml".source = configFile;
  };
}
