{
  lib,
  config,
  ...
}:
let
  cfg = config.khscodes.openstack;
  firewallRuleModule = lib.khscodes.mkSubmodule {
    description = "Firewall rule";
    options = {
      direction = lib.mkOption {
        type = lib.types.enum [
          "ingress"
          "egress"
        ];
        description = "The direction of the firewall rule";
      };
      ethertype = lib.mkOption {
        type = lib.types.enum [
          "IPv4"
          "IPv6"
        ];
        description = "The IP version";
      };
      protocol = lib.mkOption {
        type = lib.types.enum [
          "tcp"
          "udp"
          "icmp"
        ];
        description = "The protocol";
      };
      port = lib.mkOption {
        type = lib.types.int;
        description = "The port (for udp and tcp rules) to apply the rule to";
      };
      remote_subnet = lib.mkOption {
        type = lib.types.str;
        description = "The remote subnet to apply the rule to";
      };
    };
  };
  mapFirewallRule =
    security_group_id: rule:
    {
      inherit (rule) direction ethertype;
      inherit security_group_id;
      protocol =
        if rule.ethertype == "IPv6" && rule.protocol == "icmp" then "ipv6-icmp" else rule.protocol;
      remote_ip_prefix = rule.remote_subnet;
    }
    // (lib.attrsets.optionalAttrs (rule.protocol != "icmp") {
      port_range_min = rule.port;
      port_range_max = rule.port;
    });
  openstackComputeInstance = lib.khscodes.mkSubmodule {
    description = "Openstack compute instance";
    options = {
      name = lib.mkOption {
        type = lib.types.str;
        description = "Name of the instance";
      };
      flavor = lib.mkOption {
        type = lib.types.str;
        description = "Flavor of the instance";
      };
      initial_image = lib.mkOption {
        type = lib.types.str;
        description = "Initial image of the server";
      };
      ssh_public_key = lib.mkOption {
        type = lib.types.str;
        description = "The ssh key added to the server";
      };
      ip4_cidr = lib.mkOption {
        type = lib.types.str;
        description = "IPv4 cidr of the private virtual network";
        default = "172.24.0.0/24";
      };
      ip4_dns_nameservers = lib.mkOption {
        type = lib.types.listOf lib.types.str;
        description = "DNS (IPv4) nameservers to set (in DHCP) for the ipv4 subnet";
        default = [
          "1.1.1.1"
          "1.0.0.1"
        ];
      };
      user_data = lib.mkOption {
        type = lib.types.str;
        default = "";
        description = "User data for the instance";
      };
      volume_size = lib.mkOption {
        type = lib.types.int;
        description = "Size of the root volume, in gigabytes";
        default = 30;
      };
      volume_type = lib.mkOption {
        type = lib.types.enum [
          "Encrypted"
          "__DEFAULT__"
        ];
        description = "The type of volume to create";
        default = "Encrypted";
      };
      ip6_dns_nameservers = lib.mkOption {
        type = lib.types.listOf lib.types.str;
        description = "DNS (IPv6) nameservers to set (in DHCP) for the ipv4 subnet";
        default = [
          "2606:4700:4700::1111"
          "2606:4700:4700::1001"
        ];
      };
      tags = lib.mkOption {
        type = lib.types.listOf lib.types.str;
      };
      firewall_rules = lib.mkOption {
        type = lib.types.listOf firewallRuleModule;
        description = "List of firewall rules to apply to the server";
        default = [ ];
      };
    };
  };
in
{
  options.khscodes.openstack = {
    enable = lib.mkEnableOption "Enables the openstack provider";
    compute_instance = lib.mkOption {
      type = lib.types.attrsOf openstackComputeInstance;
      description = "Defines an openstack compute instance";
      default = { };
    };
  };

  imports = [ ./output.nix ];

  config = lib.mkIf cfg.enable {
    terraform.required_providers.openstack = {
      source = "terraform-provider-openstack/openstack";
      version = "~> 3.2.0";
    };
    provider.openstack = {
      user_name = "\${ var.openstack_username }";
      tenant_name = "\${ var.openstack_tenant_name }";
      password = "\${ var.openstack_password }";
      auth_url = "\${ var.openstack_auth_url }";
      region = "\${ var.openstack_region }";
      endpoint_type = "\${ var.openstack_endpoint_type }";
    };
    variable = {
      openstack_username = {
        type = "string";
      };
      openstack_password = {
        type = "string";
        sensitive = true;
      };
      openstack_tenant_name = {
        type = "string";
      };
      openstack_auth_url = {
        type = "string";
      };
      openstack_endpoint_type = {
        type = "string";
      };
      openstack_region = {
        type = "string";
      };
    };
    # DATA

    # flavor
    data.openstack_compute_flavor_v2 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          name = value.flavor;
        };
      }
    ) cfg.compute_instance;

    # image
    data.openstack_images_image_v2 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          name = value.initial_image;
        };
      }
    ) cfg.compute_instance;

    # provider network
    data.openstack_networking_network_v2.provider = {
      name = "public";
    };

    # provider subnet pool (for ipv6)
    data.openstack_networking_subnetpool_v2.provider = {
      name = "provider-subnet-pool";
    };

    # RESOURCE

    # keypair
    resource.openstack_compute_keypair_v2 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          name = lib.khscodes.sanitize-terraform-name value.name;
          public_key = value.ssh_public_key;
        };
      }
    ) cfg.compute_instance;

    # router
    resource.openstack_networking_router_v2 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          name = value.name;
          external_network_id = "\${ data.openstack_networking_network_v2.provider.id }";
          tags = value.tags;
        };
      }
    ) cfg.compute_instance;

    # network
    resource.openstack_networking_network_v2 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          name = value.name;
          tags = value.tags;
        };
      }
    ) cfg.compute_instance;

    # subnet
    resource.openstack_networking_subnet_v2 =
      (lib.mapAttrs' (
        name: value:
        let
          sanitizedName = lib.khscodes.sanitize-terraform-name name;
        in
        {
          name = "${sanitizedName}_ip4";
          value = {
            name = "ip4: ${value.name}";
            cidr = value.ip4_cidr;
            ip_version = 4;
            network_id = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
            dns_nameservers = value.ip4_dns_nameservers;
            tags = value.tags;
          };
        }
      ) cfg.compute_instance)
      // (lib.mapAttrs' (
        name: value:
        let
          sanitizedName = lib.khscodes.sanitize-terraform-name name;
        in
        {
          name = "${sanitizedName}_ip6";
          value = {
            name = "ip6: ${value.name}";
            ip_version = 6;
            ipv6_address_mode = "dhcpv6-stateless";
            ipv6_ra_mode = "dhcpv6-stateless";
            subnetpool_id = "\${ data.openstack_networking_subnetpool_v2.provider.id }";
            dns_nameservers = value.ip6_dns_nameservers;
            network_id = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
            tags = value.tags;
          };
        }
      ) cfg.compute_instance);

    # router interface
    resource.openstack_networking_router_interface_v2 =
      (lib.mapAttrs' (
        name: value:
        let
          sanitizedName = lib.khscodes.sanitize-terraform-name name;
        in
        {
          name = "${sanitizedName}_ip4";
          value = {
            router_id = "\${ openstack_networking_router_v2.${sanitizedName}.id }";
            subnet_id = "\${ openstack_networking_subnet_v2.${sanitizedName}_ip4.id }";
          };
        }
      ) cfg.compute_instance)
      // (lib.mapAttrs' (
        name: value:
        let
          sanitizedName = lib.khscodes.sanitize-terraform-name name;
        in
        {
          name = "${sanitizedName}_ip6";
          value = {
            router_id = "\${ openstack_networking_router_v2.${sanitizedName}.id }";
            subnet_id = "\${ openstack_networking_subnet_v2.${sanitizedName}_ip6.id }";
          };
        }
      ) cfg.compute_instance);

    # floating ip
    resource.openstack_networking_floatingip_v2 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          pool = "public";
          tags = value.tags;
        };
      }
    ) cfg.compute_instance;

    # volume
    resource.openstack_blockstorage_volume_v3 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          name = value.name;
          size = value.volume_size;
          image_id = "\${ data.openstack_images_image_v2.${sanitizedName}.id }";
          volume_type = value.volume_type;
        };
      }
    ) cfg.compute_instance;

    # security group
    resource.openstack_networking_secgroup_v2 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          name = value.name;
          tags = value.tags;
        };
      }
    ) cfg.compute_instance;

    # security group rules (firewall rules)
    resource.openstack_networking_secgroup_rule_v2 = lib.attrsets.mergeAttrsList (
      lib.lists.flatten (
        lib.lists.map (
          { name, value }:
          let
            sanitizedName = lib.khscodes.sanitize-terraform-name name;
          in
          lib.listToAttrs (
            lib.lists.map (
              rule:
              let
                protocol =
                  if rule.protocol == "icmp" then "icmp" else "${rule.protocol}_${builtins.toString rule.port}";
              in
              {
                name = "${sanitizedName}_${rule.direction}_${rule.ethertype}_${protocol}_${lib.khscodes.sanitize-terraform-name rule.remote_subnet}";
                value = mapFirewallRule "\${ resource.openstack_networking_secgroup_v2.${sanitizedName}.id }" rule;
              }
            ) value.firewall_rules
          )
        ) (lib.attrsToList cfg.compute_instance)
      )
    );

    # instance
    data.openstack_networking_port_v2 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          device_id = "\${ openstack_compute_instance_v2.${sanitizedName}.id }";
          network_id = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
        };
      }
    ) cfg.compute_instance;
    resource.openstack_compute_instance_v2 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          name = value.name;
          tags = value.tags;
          flavor_id = "\${ data.openstack_compute_flavor_v2.${sanitizedName}.id }";
          key_pair = "\${ openstack_compute_keypair_v2.${sanitizedName}.name }";
          block_device = [
            {
              uuid = "\${ openstack_blockstorage_volume_v3.${sanitizedName}.id }";
              source_type = "volume";
              boot_index = 0;
              destination_type = "volume";
              delete_on_termination = false;
            }
          ];
          security_groups = [ "\${ openstack_networking_secgroup_v2.${sanitizedName}.name }" ];
          network = [
            {
              uuid = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
            }
          ];
          user_data = value.user_data;
        };
      }
    ) cfg.compute_instance;

    # floating ip associate
    resource.openstack_networking_floatingip_associate_v2 = lib.mapAttrs' (
      name: value:
      let
        sanitizedName = lib.khscodes.sanitize-terraform-name name;
      in
      {
        name = sanitizedName;
        value = {
          floating_ip = "\${ openstack_networking_floatingip_v2.${sanitizedName}.address }";
          port_id = "\${ data.openstack_networking_port_v2.${sanitizedName}.id }";
        };
      }
    ) cfg.compute_instance;
  };
}
