{ config, lib, ... }:
let
  cfg = config.khscodes.cloudflare;
  nameFromFQDNAndZone =
    fqdn: zone:
    let
      stripped = lib.strings.removeSuffix ".${zone}" fqdn;
    in
    if stripped != fqdn then
      stripped
    else if fqdn == zone then
      "@"
    else
      fqdn;
  tldFromFqdn =
    fqdn:
    let
      split = lib.strings.splitString "." fqdn;
      top = lib.lists.takeEnd 2 split;
    in
    lib.strings.concatStringsSep "." top;
  serviceFromFqdn =
    fqdn:
    let
      split = lib.strings.splitString "." fqdn;
    in
    assert
      lib.strings.hasPrefix "_" (builtins.elemAt split 0)
      && lib.strings.hasPrefix "_" (builtins.elemAt split 1);
    builtins.elemAt split 0;
  protocolFromFqdn =
    fqdn:
    let
      split = lib.strings.splitString "." fqdn;
    in
    assert
      lib.strings.hasPrefix "_" (builtins.elemAt split 0)
      && lib.strings.hasPrefix "_" (builtins.elemAt split 1);
    builtins.elemAt split 1;
  nameFromFqdn =
    fqdn:
    let
      split = lib.strings.splitString "." fqdn;
    in
    assert
      lib.strings.hasPrefix "_" (builtins.elemAt split 0)
      && lib.strings.hasPrefix "_" (builtins.elemAt split 1);
    lib.strings.concatStringsSep "." (
      lib.lists.removePrefix [ (builtins.elemAt split 0) (builtins.elemAt split 1) ] split
    );
  dnsARecordModule = lib.khscodes.mkSubmodule {
    description = "Module for defining dns A/AAAA record";
    options = {
      fqdn = lib.mkOption {
        type = lib.types.str;
        description = "The FQDN of the A/AAAA record to create";
      };
      content = lib.mkOption {
        type = lib.types.str;
        description = "The content of the A/AAAA record (IPv4/IPv6 address)";
      };
      proxied = lib.mkOption {
        type = lib.types.bool;
        description = "Creates a proxied record in cloudflare";
        default = false;
      };
      ttl = lib.mkOption {
        type = lib.types.int;
        description = "Time to Live for the A/AAAA record";
        default = 600;
      };
    };
  };
  dnsCnameRecordModule = lib.khscodes.mkSubmodule {
    description = "Module for defining dns CNAME record";
    options = {
      fqdn = lib.mkOption {
        type = lib.types.str;
        description = "The FQDN of the CNAME record to create";
      };
      content = lib.mkOption {
        type = lib.types.str;
        description = "The content of the CNAME record (canonical name)";
      };
      proxied = lib.mkOption {
        type = lib.types.bool;
        description = "Creates a proxied record in cloudflare";
        default = false;
      };
      ttl = lib.mkOption {
        type = lib.types.int;
        description = "Time to Live for the CNAME record";
        default = 600;
      };
    };
  };
  dnsTxtRecordModule = lib.khscodes.mkSubmodule {
    description = "Module for defining dns TXT record";
    options = {
      fqdn = lib.mkOption {
        type = lib.types.str;
        description = "The FQDN of the TXT record to create";
      };
      content = lib.mkOption {
        type = lib.types.str;
        description = "The content of the TXT record";
      };
      ttl = lib.mkOption {
        type = lib.types.int;
        description = "Time to Live for the TXT record";
        default = 600;
      };
    };
  };
  dnsSrvRecordModule = lib.khscodes.mkSubmodule {
    description = "Module for defining dns SRV record";
    options = {
      fqdn = lib.mkOption {
        type = lib.types.str;
        description = "The FQDN of the SRV record to create";
      };
      content = lib.mkOption {
        type = lib.types.str;
        description = "The content of the SRV record";
      };
      priority = lib.mkOption {
        type = lib.types.int;
        description = "Priority for the SRV record";
      };
      weight = lib.mkOption {
        type = lib.types.int;
        description = "Weight for the SRV record";
      };
      port = lib.mkOption {
        type = lib.types.int;
        description = "Port for the SRV record";
      };
      ttl = lib.mkOption {
        type = lib.types.int;
        description = "Time to Live for the SRV record";
        default = 600;
      };
    };
  };
  dnsTlsaRecordModule = lib.khscodes.mkSubmodule {
    description = "Module for defining dns TLSA record";
    options = {
      fqdn = lib.mkOption {
        type = lib.types.str;
        description = "The FQDN of the TLSA record to create";
      };
      content = lib.mkOption {
        type = lib.types.str;
        description = "The content of the TLSA record";
      };
      ttl = lib.mkOption {
        type = lib.types.int;
        description = "Time to Live for the TLSA record";
        default = 600;
      };
    };
  };
  dnsMxRecordModule = lib.khscodes.mkSubmodule {
    description = "Module for defining dns MX record";
    options = {
      fqdn = lib.mkOption {
        type = lib.types.str;
        description = "The FQDN of the MX record to create";
      };
      content = lib.mkOption {
        type = lib.types.str;
        description = "The content of the MX record";
      };
      priority = lib.mkOption {
        type = lib.types.int;
        description = "Priority for the MX record";
      };
      ttl = lib.mkOption {
        type = lib.types.int;
        description = "Time to Live for the MX record";
        default = 600;
      };
    };
  };
in
{
  imports = [ ./dns_zone.nix ];
  options.khscodes.cloudflare = {
    enable = lib.mkEnableOption "Enables khscodes cloudflare terranix integration";
    dns = {
      enable = lib.mkEnableOption "Enables setting up DNS records";
      aRecords = lib.mkOption {
        type = lib.types.listOf dnsARecordModule;
        default = [ ];
        description = "A records to create in the zone";
      };
      aaaaRecords = lib.mkOption {
        type = lib.types.listOf dnsARecordModule;
        default = [ ];
        description = "AAAA records to create in the zone";
      };
      cnameRecords = lib.mkOption {
        type = lib.types.listOf dnsCnameRecordModule;
        default = [ ];
        description = "CNAME records to create in the zone";
      };
      txtRecords = lib.mkOption {
        type = lib.types.listOf dnsTxtRecordModule;
        default = [ ];
        description = "TXT Records to create";
      };
      srvRecords = lib.mkOption {
        type = lib.types.listOf dnsSrvRecordModule;
        default = [ ];
        description = "SRV Records to create";
      };
      tlsaRecords = lib.mkOption {
        type = lib.types.listOf dnsTlsaRecordModule;
        default = [ ];
        description = "TLSA Records to create";
      };
      mxRecords = lib.mkOption {
        type = lib.types.listOf dnsMxRecordModule;
        default = [ ];
        description = "MX records to create";
      };
    };
  };

  config = lib.mkIf cfg.enable {
    provider.cloudflare.api_token = "\${ var.cloudflare_token }";
    variable.cloudflare_token = {
      type = "string";
      sensitive = true;
    };
    terraform.required_providers.cloudflare = {
      source = "cloudflare/cloudflare";
      version = "~> 4.0";
    };

    khscodes.cloudflare.data.dns_zones = lib.lists.unique (
      lib.lists.map (a: tldFromFqdn (a.fqdn)) (
        cfg.dns.aRecords
        ++ cfg.dns.aaaaRecords
        ++ cfg.dns.txtRecords
        ++ cfg.dns.mxRecords
        ++ cfg.dns.cnameRecords
        ++ cfg.dns.srvRecords
        ++ cfg.dns.tlsaRecords
        ++ cfg.dns.cnameRecords
      )
    );
    resource.cloudflare_record = lib.attrsets.optionalAttrs cfg.dns.enable (
      lib.listToAttrs (
        (lib.lists.map (
          record:
          let
            zoneName = tldFromFqdn record.fqdn;
          in
          {
            name = "${lib.khscodes.sanitize-terraform-name record.fqdn}_a";
            value = {
              inherit (record) content ttl proxied;
              name = nameFromFQDNAndZone record.fqdn zoneName;
              type = "A";
              zone_id = "\${ data.cloudflare_zone.${lib.khscodes.sanitize-terraform-name zoneName}.id }";
              comment = "app=${zoneName}";
            };
          }
        ) cfg.dns.aRecords)
        ++ (lib.lists.map (
          record:
          let
            zoneName = tldFromFqdn record.fqdn;
          in
          {
            name = "${lib.khscodes.sanitize-terraform-name record.fqdn}_aaaa";
            value = {
              inherit (record) content ttl proxied;
              name = nameFromFQDNAndZone record.fqdn zoneName;
              type = "AAAA";
              zone_id = "\${ data.cloudflare_zone.${lib.khscodes.sanitize-terraform-name zoneName}.id }";
              comment = "app=${zoneName}";
            };
          }
        ) cfg.dns.aaaaRecords)
        ++ (lib.lists.map (
          record:
          let
            zoneName = tldFromFqdn record.fqdn;
          in
          {
            name = "${lib.khscodes.sanitize-terraform-name record.fqdn}_cname";
            value = {
              inherit (record) content ttl proxied;
              name = nameFromFQDNAndZone record.fqdn zoneName;
              type = "CNAME";
              zone_id = "\${ data.cloudflare_zone.${lib.khscodes.sanitize-terraform-name zoneName}.id }";
              comment = "app=${zoneName}";
            };
          }
        ) cfg.dns.cnameRecords)
        ++ (lib.lists.map (
          record:
          let
            zoneName = tldFromFqdn record.fqdn;
          in
          {
            name = "${lib.khscodes.sanitize-terraform-name record.fqdn}_txt";
            value = {
              inherit (record) content ttl;
              name = nameFromFQDNAndZone record.fqdn zoneName;
              type = "TXT";
              zone_id = "\${ data.cloudflare_zone.${lib.khscodes.sanitize-terraform-name zoneName}.id }";
              comment = "app=${zoneName}";
            };
          }
        ) cfg.dns.txtRecords)
        ++ (lib.lists.map (
          record:
          let
            zoneName = tldFromFqdn record.fqdn;
          in
          {
            name = "${lib.khscodes.sanitize-terraform-name record.fqdn}_mx";
            value = {
              inherit (record) content priority;
              name = nameFromFQDNAndZone record.fqdn zoneName;
              type = "MX";
              zone_id = "\${ data.cloudflare_zone.${lib.khscodes.sanitize-terraform-name zoneName}.id }";
              comment = "app=${zoneName}";
            };
          }
        ) cfg.dns.mxRecords)
        ++ (lib.lists.map (
          record:
          let
            zoneName = tldFromFqdn record.fqdn;
          in
          {
            name = "${lib.khscodes.sanitize-terraform-name record.fqdn}_srv";
            value = {
              name = nameFromFQDNAndZone record.fqdn zoneName;
              type = "SRV";
              zone_id = "\${ data.cloudflare_zone.${lib.khscodes.sanitize-terraform-name zoneName}.id }";
              comment = "app=${zoneName}";
              data = {
                inherit (record) priority weight port;
                target = record.content;
              };
            };
          }
        ) cfg.dns.srvRecords)
        ++ (lib.lists.map (
          record:
          let
            zoneName = tldFromFqdn record.fqdn;
          in
          {
            name = "${lib.khscodes.sanitize-terraform-name record.fqdn}_tlsa";
            value = {
              inherit (record) content;
              name = nameFromFQDNAndZone record.fqdn zoneName;
              type = "TLSA";
              zone_id = "\${ data.cloudflare_zone.${lib.khscodes.sanitize-terraform-name zoneName}.id }";
              comment = "app=${zoneName}";
            };
          }
        ) cfg.dns.tlsaRecords)
      )
    );
  };
}
