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

with lib;

let
  cfg = config.plasmacloud.cluster;
  clusterConfigLib = import ../../nix-nos/lib/cluster-config-lib.nix { inherit lib; };
  nodeType = clusterConfigLib.mkNodeType types;
  nodeClassType = clusterConfigLib.mkNodeClassType types;
  nodePoolType = clusterConfigLib.mkNodePoolType types;
  enrollmentRuleType = clusterConfigLib.mkEnrollmentRuleType types;
  hostDeploymentType = clusterConfigLib.mkHostDeploymentType types;
  serviceType = clusterConfigLib.mkServiceType types;
  mtlsPolicyType = clusterConfigLib.mkMtlsPolicyType types;
  jsonFormat = pkgs.formats.json { };

  # Generate cluster-config.json for the current node
  generateClusterConfig = cluster:
    clusterConfigLib.mkClusterConfig {
      inherit cluster;
      hostname = config.networking.hostName;
      bootstrapNodeName = head cluster.bootstrap.initialPeers;
    };

  generatedNodeClusterConfig =
    if cfg.nodes ? "${config.networking.hostName}" then
      generateClusterConfig cfg
    else
      null;

  generatedNixNOSTopologyCluster = clusterConfigLib.mkNixNOSTopologyCluster cfg;
  generatedDeployerClusterState = clusterConfigLib.mkDeployerClusterState cfg;

in {
  options.plasmacloud.cluster = {
    enable = mkEnableOption "PlasmaCloud cluster configuration";

    name = mkOption {
      type = types.str;
      default = "plasmacloud-cluster";
      description = "Cluster name";
    };

    nodes = mkOption {
      type = types.attrsOf nodeType;
      default = {};
      description = "Map of node names to their configurations";
      example = literalExpression ''
        {
          "node01" = {
            role = "control-plane";
            ip = "10.0.1.10";
            services = [ "chainfire" "flaredb" "iam" ];
          };
        }
      '';
    };

    bootstrap = {
      initialPeers = mkOption {
        type = types.listOf types.str;
        description = "Initial Raft peers for bootstrap (ordered list, first is bootstrap node)";
        example = [ "node01" "node02" "node03" ];
      };
    };

    bgp = {
      asn = mkOption {
        type = types.int;
        description = "BGP AS number for the cluster";
        example = 64512;
      };
    };

    deployer = {
      clusterId = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Cluster ID exported into deployer cluster state";
      };

      environment = mkOption {
        type = types.nullOr types.str;
        default = null;
        description = "Optional environment label exported into deployer cluster state";
      };

      nodeClasses = mkOption {
        type = types.attrsOf nodeClassType;
        default = { };
        description = "Reusable deployer node classes derived from Nix";
      };

      pools = mkOption {
        type = types.attrsOf nodePoolType;
        default = { };
        description = "Reusable deployer node pools derived from Nix";
      };

      enrollmentRules = mkOption {
        type = types.attrsOf enrollmentRuleType;
        default = { };
        description = "Deployer auto-enrollment rules derived from Nix";
      };

      hostDeployments = mkOption {
        type = types.attrsOf hostDeploymentType;
        default = { };
        description = "Declarative host rollout objects derived from Nix";
      };

      services = mkOption {
        type = types.attrsOf serviceType;
        default = { };
        description = "Scheduler-managed service definitions derived from Nix";
      };

      mtlsPolicies = mkOption {
        type = types.attrsOf mtlsPolicyType;
        default = { };
        description = "Declarative mTLS policies derived from Nix";
      };
    };

    generated = {
      nodeClusterConfig = mkOption {
        type = types.nullOr (types.attrsOf types.anything);
        internal = true;
        default = null;
        description = "Resolved per-node cluster-config.json content";
      };

      deployerClusterState = mkOption {
        type = types.attrsOf types.anything;
        internal = true;
        default = { };
        description = "Resolved deployer cluster state exported from Nix";
      };
    };
  };

  config = mkIf cfg.enable {
    # Assertions
    assertions = [
      {
        assertion = (length (attrNames cfg.nodes)) > 0;
        message = "plasmacloud.cluster.nodes must contain at least one node";
      }
      {
        assertion = (length cfg.bootstrap.initialPeers) > 0;
        message = "plasmacloud.cluster.bootstrap.initialPeers must contain at least one node";
      }
      {
        assertion = all (peer: cfg.nodes ? "${peer}") cfg.bootstrap.initialPeers;
        message = "All nodes in bootstrap.initialPeers must exist in cluster.nodes";
      }
      {
        assertion = cfg.bgp.asn > 0 && cfg.bgp.asn < 4294967296;
        message = "BGP ASN must be between 1 and 4294967295";
      }
      {
        assertion = all (nodeName:
          let
            node = cfg.nodes.${nodeName};
          in
            node.pool == null || cfg.deployer.pools ? "${node.pool}"
        ) (attrNames cfg.nodes);
        message = "All node pools referenced in plasmacloud.cluster.nodes must exist in plasmacloud.cluster.deployer.pools";
      }
      {
        assertion = all (nodeName:
          let
            node = cfg.nodes.${nodeName};
          in
            node.nodeClass == null || cfg.deployer.nodeClasses ? "${node.nodeClass}"
        ) (attrNames cfg.nodes);
        message = "All node classes referenced in plasmacloud.cluster.nodes must exist in plasmacloud.cluster.deployer.nodeClasses";
      }
      {
        assertion = all (poolName:
          let
            pool = cfg.deployer.pools.${poolName};
          in
            pool.nodeClass == null || cfg.deployer.nodeClasses ? "${pool.nodeClass}"
        ) (attrNames cfg.deployer.pools);
        message = "All deployer pools must reference existing deployer node classes";
      }
      {
        assertion = all (ruleName:
          let
            rule = cfg.deployer.enrollmentRules.${ruleName};
          in
            (rule.pool == null || cfg.deployer.pools ? "${rule.pool}")
            && (rule.nodeClass == null || cfg.deployer.nodeClasses ? "${rule.nodeClass}")
        ) (attrNames cfg.deployer.enrollmentRules);
        message = "All deployer enrollment rules must reference existing pools and node classes";
      }
      {
        assertion = all (deploymentName:
          let
            deployment = cfg.deployer.hostDeployments.${deploymentName};
          in
            all (pool: cfg.deployer.pools ? "${pool}") deployment.selector.pools
            && all (nodeClass: cfg.deployer.nodeClasses ? "${nodeClass}") deployment.selector.nodeClasses
        ) (attrNames cfg.deployer.hostDeployments);
        message = "All deployer host deployments must reference existing pools and node classes";
      }
    ];

    # Generate cluster-config.json for first-boot-automation
    environment.etc."nixos/secrets/cluster-config.json" = mkIf (cfg.nodes ? "${config.networking.hostName}") {
      text = builtins.toJSON generatedNodeClusterConfig;
      mode = "0600";
    };

    nix-nos.enable = mkDefault true;
    nix-nos.clusters = {
      "${cfg.name}" = mkDefault generatedNixNOSTopologyCluster;
    };

    plasmacloud.cluster.generated.nodeClusterConfig = generatedNodeClusterConfig;
    plasmacloud.cluster.generated.deployerClusterState = generatedDeployerClusterState;

    system.build.plasmacloudDeployerClusterState =
      jsonFormat.generate "plasmacloud-deployer-cluster-state.json" generatedDeployerClusterState;
  };
}
