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

with lib;

let
  jsonFormat = pkgs.formats.json {};

  lbBackendType = types.submodule {
    options = {
      name = mkOption {
        type = types.str;
        description = "Backend name";
      };

      address = mkOption {
        type = types.str;
        description = "Backend IP or hostname";
      };

      port = mkOption {
        type = types.port;
        description = "Backend port";
      };

      weight = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "Backend weight (default: 1)";
      };

      admin_state = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Backend admin state (enabled, disabled, drain)";
      };
    };
  };

  lbSessionPersistenceType = types.submodule {
    options = {
      type = mkOption {
        type = types.enum [ "source_ip" "cookie" "app_cookie" ];
        description = "Persistence strategy";
      };

      cookie_name = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Cookie name for cookie-based persistence";
      };

      timeout_seconds = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "Persistence timeout in seconds";
      };
    };
  };

  lbHttpHealthType = types.submodule {
    options = {
      method = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "HTTP method for health checks";
      };

      path = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "HTTP path for health checks";
      };

      expected_codes = mkOption {
        type = types.listOf types.int;
        default = [];
        description = "Expected HTTP status codes";
      };

      host = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Host header for HTTP health checks";
      };
    };
  };

  lbHealthCheckType = types.submodule {
    options = {
      name = mkOption {
        type = types.str;
        description = "Health check name";
      };

      type = mkOption {
        type = types.enum [ "tcp" "http" "https" "udp" "ping" ];
        description = "Health check type";
      };

      interval_seconds = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "Interval in seconds";
      };

      timeout_seconds = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "Timeout in seconds";
      };

      healthy_threshold = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "Healthy threshold";
      };

      unhealthy_threshold = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "Unhealthy threshold";
      };

      http = mkOption {
        type = types.nullOr lbHttpHealthType;
        default = null;
        description = "HTTP-specific health check configuration";
      };

      enabled = mkOption {
        type = types.nullOr types.bool;
        default = null;
        description = "Enable or disable health check";
      };
    };
  };

  lbPoolType = types.submodule {
    options = {
      name = mkOption {
        type = types.str;
        description = "Pool name";
      };

      algorithm = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Pool algorithm (round_robin, least_connections, ip_hash, weighted_round_robin, random, maglev)";
      };

      protocol = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Pool protocol (tcp, udp, http, https)";
      };

      session_persistence = mkOption {
        type = types.nullOr lbSessionPersistenceType;
        default = null;
        description = "Session persistence configuration";
      };

      backends = mkOption {
        type = types.listOf lbBackendType;
        default = [];
        description = "Pool backends";
      };

      health_checks = mkOption {
        type = types.listOf lbHealthCheckType;
        default = [];
        description = "Pool health checks";
      };
    };
  };

  lbTlsType = types.submodule {
    options = {
      certificate_id = mkOption {
        type = types.str;
        description = "Certificate ID (FiberLB)";
      };

      min_version = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "TLS minimum version (tls_1_2, tls_1_3)";
      };

      cipher_suites = mkOption {
        type = types.listOf types.str;
        default = [];
        description = "TLS cipher suites";
      };
    };
  };

  lbL7RuleType = types.submodule {
    options = {
      type = mkOption {
        type = types.enum [ "host_name" "path" "file_type" "header" "cookie" "ssl_conn_has_sni" ];
        description = "L7 rule type";
      };

      compare_type = mkOption {
        type = types.nullOr (types.enum [ "equal_to" "regex" "starts_with" "ends_with" "contains" ]);
        default = null;
        description = "L7 compare type";
      };

      value = mkOption {
        type = types.str;
        description = "L7 rule match value";
      };

      key = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Header/cookie key for L7 rules";
      };

      invert = mkOption {
        type = types.nullOr types.bool;
        default = null;
        description = "Invert the rule match";
      };
    };
  };

  lbL7PolicyType = types.submodule {
    options = {
      name = mkOption {
        type = types.str;
        description = "L7 policy name";
      };

      position = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "L7 policy position (lower evaluates first)";
      };

      action = mkOption {
        type = types.enum [ "redirect_to_pool" "redirect_to_url" "reject" ];
        description = "L7 policy action";
      };

      redirect_url = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Redirect URL for redirect_to_url action";
      };

      redirect_pool = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Target pool name for redirect_to_pool action";
      };

      redirect_http_status_code = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "Redirect or reject HTTP status code";
      };

      enabled = mkOption {
        type = types.nullOr types.bool;
        default = null;
        description = "Enable or disable the policy";
      };

      rules = mkOption {
        type = types.listOf lbL7RuleType;
        default = [];
        description = "L7 rules for this policy";
      };
    };
  };

  lbListenerType = types.submodule {
    options = {
      name = mkOption {
        type = types.str;
        description = "Listener name";
      };

      protocol = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Listener protocol (tcp, udp, http, https, terminated_https)";
      };

      port = mkOption {
        type = types.port;
        description = "Listener port";
      };

      default_pool = mkOption {
        type = types.str;
        description = "Default pool name";
      };

      tls = mkOption {
        type = types.nullOr lbTlsType;
        default = null;
        description = "TLS configuration";
      };

      connection_limit = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "Connection limit";
      };

      enabled = mkOption {
        type = types.nullOr types.bool;
        default = null;
        description = "Enable or disable listener";
      };

      l7_policies = mkOption {
        type = types.listOf lbL7PolicyType;
        default = [];
        description = "L7 policies for this listener";
      };
    };
  };

  lbType = types.submodule {
    options = {
      name = mkOption {
        type = types.str;
        description = "Load balancer name";
      };

      org_id = mkOption {
        type = types.str;
        description = "Organization ID";
      };

      project_id = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Project ID (null for org-level)";
      };

      description = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Load balancer description";
      };

      pools = mkOption {
        type = types.listOf lbPoolType;
        default = [];
        description = "Pools for this load balancer";
      };

      listeners = mkOption {
        type = types.listOf lbListenerType;
        default = [];
        description = "Listeners for this load balancer";
      };
    };
  };

  dnsRecordType = types.submodule {
    options = {
      name = mkOption {
        type = types.str;
        description = "Record name";
      };

      record_type = mkOption {
        type = types.str;
        description = "Record type (A, AAAA, CNAME, MX, TXT, SRV, NS, PTR, CAA)";
      };

      ttl = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "Record TTL (default: 300)";
      };

      data = mkOption {
        type = types.attrsOf types.anything;
        description = "Record data (type-specific)";
      };

      enabled = mkOption {
        type = types.nullOr types.bool;
        default = null;
        description = "Enable or disable record";
      };
    };
  };

  dnsZoneType = types.submodule {
    options = {
      name = mkOption {
        type = types.str;
        description = "Zone name (example.com)";
      };

      org_id = mkOption {
        type = types.str;
        description = "Organization ID";
      };

      project_id = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Project ID (null for org-level)";
      };

      primary_ns = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Primary NS hostname";
      };

      admin_email = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Admin email for SOA";
      };

      refresh = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "SOA refresh";
      };

      retry = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "SOA retry";
      };

      expire = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "SOA expire";
      };

      minimum = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "SOA minimum";
      };

      records = mkOption {
        type = types.listOf dnsRecordType;
        default = [];
        description = "Zone records";
      };
    };
  };

  dnsReverseZoneType = types.submodule {
    options = {
      org_id = mkOption {
        type = types.str;
        description = "Organization ID";
      };

      project_id = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Project ID (null for org-level)";
      };

      cidr = mkOption {
        type = types.str;
        description = "CIDR for the reverse zone";
      };

      ptr_pattern = mkOption {
        type = types.str;
        description = "PTR pattern (e.g., {4}-{3}-{2}-{1}.hosts.example.com.)";
      };

      ttl = mkOption {
        type = types.nullOr types.int;
        default = null;
        description = "PTR TTL";
      };
    };
  };

  lbCfg = config.plasmacloud.lb;
  dnsCfg = config.plasmacloud.dns;

  lbConfigFile = jsonFormat.generate "plasmacloud-lb.json" {
    load_balancers = lbCfg.loadBalancers;
  };
  lbConfigPath = lbCfg.configPath;
  lbConfigRelative = removePrefix "/etc/" lbConfigPath;

  dnsConfigFile = jsonFormat.generate "plasmacloud-dns.json" {
    zones = dnsCfg.zones;
    reverse_zones = dnsCfg.reverseZones;
  };
  dnsConfigPath = dnsCfg.configPath;
  dnsConfigRelative = removePrefix "/etc/" dnsConfigPath;

in {
  options.plasmacloud.lb = {
    enable = mkEnableOption "PlasmaCloud load balancer declarations";

    endpoint = mkOption {
      type = types.str;
      default = "http://127.0.0.1:7000";
      description = "FiberLB gRPC endpoint";
    };

    loadBalancers = mkOption {
      type = types.listOf lbType;
      default = [];
      description = "Load balancer declarations";
    };

    configPath = mkOption {
      type = types.str;
      default = "/etc/plasmacloud/lb.json";
      description = "Path for rendered load balancer config";
    };

    applyOnBoot = mkOption {
      type = types.bool;
      default = true;
      description = "Apply declarations at boot";
    };

    applyOnChange = mkOption {
      type = types.bool;
      default = true;
      description = "Apply declarations when the config file changes";
    };

    prune = mkOption {
      type = types.bool;
      default = false;
      description = "Delete load balancer resources not declared in config";
    };

    package = mkOption {
      type = types.package;
      default = pkgs.plasmacloud-reconciler or (throw "plasmacloud-reconciler package not found");
      description = "Reconciler package for load balancer declarations";
    };
  };

  options.plasmacloud.dns = {
    enable = mkEnableOption "PlasmaCloud DNS declarations";

    endpoint = mkOption {
      type = types.str;
      default = "http://127.0.0.1:6000";
      description = "FlashDNS gRPC endpoint";
    };

    zones = mkOption {
      type = types.listOf dnsZoneType;
      default = [];
      description = "DNS zone declarations";
    };

    reverseZones = mkOption {
      type = types.listOf dnsReverseZoneType;
      default = [];
      description = "Reverse DNS zone declarations";
    };

    configPath = mkOption {
      type = types.str;
      default = "/etc/plasmacloud/dns.json";
      description = "Path for rendered DNS config";
    };

    applyOnBoot = mkOption {
      type = types.bool;
      default = true;
      description = "Apply declarations at boot";
    };

    applyOnChange = mkOption {
      type = types.bool;
      default = true;
      description = "Apply declarations when the config file changes";
    };

    prune = mkOption {
      type = types.bool;
      default = false;
      description = "Delete DNS resources not declared in config";
    };

    package = mkOption {
      type = types.package;
      default = pkgs.plasmacloud-reconciler or (throw "plasmacloud-reconciler package not found");
      description = "Reconciler package for DNS declarations";
    };
  };

  config = mkMerge [
    (mkIf lbCfg.enable {
      assertions = [
        {
          assertion = hasPrefix "/etc/" lbConfigPath;
          message = "plasmacloud.lb.configPath must be under /etc";
        }
      ];

      environment.etc."${lbConfigRelative}".source = lbConfigFile;

      systemd.services.plasmacloud-lb-apply = {
        description = "Apply PlasmaCloud load balancer declarations";
        after = [ "network-online.target" ] ++ (optional config.services.fiberlb.enable "fiberlb.service");
        wants = [ "network-online.target" ] ++ (optional config.services.fiberlb.enable "fiberlb.service");
        wantedBy = optional lbCfg.applyOnBoot "multi-user.target";

        serviceConfig = {
          Type = "oneshot";
          ExecStart = "${lbCfg.package}/bin/plasmacloud-reconciler lb --config ${lbConfigPath} --endpoint ${lbCfg.endpoint}${optionalString lbCfg.prune " --prune"}";
        };
      };

      systemd.paths.plasmacloud-lb-apply = mkIf lbCfg.applyOnChange {
        wantedBy = [ "multi-user.target" ];
        pathConfig = {
          PathChanged = lbConfigPath;
        };
      };
    })

    (mkIf dnsCfg.enable {
      assertions = [
        {
          assertion = hasPrefix "/etc/" dnsConfigPath;
          message = "plasmacloud.dns.configPath must be under /etc";
        }
      ];

      environment.etc."${dnsConfigRelative}".source = dnsConfigFile;

      systemd.services.plasmacloud-dns-apply = {
        description = "Apply PlasmaCloud DNS declarations";
        after = [ "network-online.target" ] ++ (optional config.services.flashdns.enable "flashdns.service");
        wants = [ "network-online.target" ] ++ (optional config.services.flashdns.enable "flashdns.service");
        wantedBy = optional dnsCfg.applyOnBoot "multi-user.target";

        serviceConfig = {
          Type = "oneshot";
          ExecStart = "${dnsCfg.package}/bin/plasmacloud-reconciler dns --config ${dnsConfigPath} --endpoint ${dnsCfg.endpoint}${optionalString dnsCfg.prune " --prune"}";
        };
      };

      systemd.paths.plasmacloud-dns-apply = mkIf dnsCfg.applyOnChange {
        wantedBy = [ "multi-user.target" ];
        pathConfig = {
          PathChanged = dnsConfigPath;
        };
      };
    })
  ];
}
