locals {
  mail_path        = "${local.persistent_folder}/mail"
  mailboxes_path   = "${local.mail_path}/mailboxes"
  getmail_path     = "${local.mail_path}/getmail"
  postfix_path     = "${local.mail_path}/postfix"
  mail_backup_path = "${local.persistent_folder}/backup.mail"

  getmail_jobs = flatten([
    for account in local.email_accounts : [
      for mailbox in account.mailboxes : {
        name         = "${account.recipient}-${mailbox}"
        recipient    = "${account.recipient}+${mailbox}"
        domain       = account.domain
        server       = account.receive.server
        port         = account.receive.port
        username     = account.username
        password     = account.password
        mailbox      = mailbox
        delete_after = try(account.delete_after, 30)
      }
    ]
  ])

  postfix_env_vars = {
    # Email configuration
    SMTP_HOST            = local.scaleway_smtp_host
    SMTP_PORT            = 465
    MY_PRIMARY_DOMAIN    = local.domain_name
    MY_DESTINATION       = join(",", [for account in local.email_accounts : account.domain])
    MY_NETWORKS          = "127.0.0.0/8"
    MAILNAME             = "smtp.${local.domain_name}"
    DOVECOT_AUTH_ADDRESS = "dovecot-auth.mail.svc.cluster.local"
    DOVECOT_AUTH_PORT    = 31000
    DOVECOT_LMTP_ADDRESS = "dovecot-lmtp.mail.svc.cluster.local"
    DOVECOT_LMTP_PORT    = 31024
    LOG_TO_STDOUT        = 0
  }

  password_hashes = {
    for account in local.email_accounts : account.username => data.external.password_hasher[account.username].result.hex_encoded_bcrypt_hash
  }

  postfix_secrets = {
    SMTP_USER = {
      key  = "username"
      name = kubernetes_secret.relay_mail_credentials.metadata[0].name
    }

    SMTP_PASSWORD = {
      key  = "password"
      name = kubernetes_secret.relay_mail_credentials.metadata[0].name
    }
  }
}

data "external" "password_hasher" {
  for_each = { for account in local.email_accounts : account.username => account }
  program  = ["python3", "${path.module}/../../lib/bcrypt_hash.py"]

  query = {
    password = each.value.password
    cost     = "10"
  }
}

resource "kubernetes_namespace" "mail" {
  metadata {
    annotations = {
      name = "mail"
    }
    labels = {
      name = "mail"
    }
    name = "mail"
  }
}

resource "kubernetes_secret" "dovecot_users" {
  metadata {
    name      = "dovecot-users"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }
  data = {
    "users" = join("\n", [for account in local.email_accounts : "${account.username}:{BLF-CRYPT.HEX}${local.password_hashes[account.username]}:1000:1000::/var/mail/${account.username}::"])
  }
}

resource "kubernetes_secret" "relay_mail_credentials" {
  metadata {
    name      = "relay-mail-credentials"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }
  data = {
    username = var.scaleway_project_id
    password = scaleway_iam_api_key.mail_app_api.secret_key
  }
  type = "Opaque"
}

resource "kubernetes_secret" "mail_crypt_certs" {
  metadata {
    name      = "mail-crypt-certs"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }
  data = {
    "ecprivkey.pem" = var.mail_crypt_private_key
    "ecpubkey.pem"  = var.mail_crypt_public_key
  }
  type = "Opaque"
}


resource "kubernetes_service_account" "mail_app" {

  metadata {
    name      = "mail-app-sa"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }

  automount_service_account_token = false
}

resource "kubernetes_config_map" "postfix_config" {
  metadata {
    name      = "postfix-config"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }

  data = {
    "run.config" = file("${path.module}/resources/mail/run.config")
  }
}


resource "kubernetes_secret" "bcc_map" {
  metadata {
    name      = "bcc-map"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }
  data = {
    "bcc_map" = join("\n", [for account in local.email_accounts : "/([^@]+)@${replace(account.domain, ".", "\\.")}/ $${1}+Sent@${account.domain}"])
  }
}

resource "kubernetes_config_map" "getmail_config" {
  metadata {
    name      = "getmail-config"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }

  data = { for job in local.getmail_jobs : "getmailrc_${job.name}" =>

    templatefile("${path.module}/resources/mail/getmailrc.tpl", {
      recipient            = job.recipient
      domain               = job.domain
      server               = job.server
      port                 = job.port
      username             = job.username
      password             = job.password
      mailbox              = job.mailbox
      delete_after         = job.delete_after
      dovecot_lmtp_address = "dovecot-lmtp.mail.svc.cluster.local"
      dovecot_lmtp_port    = 31024
    })
  }
}

resource "kubernetes_config_map" "dovecot_config" {
  metadata {
    name      = "dovecot-config"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }

  data = {
    "dovecot.conf" = file("${path.module}/resources/mail/dovecot.conf")
  }
}

resource "kubernetes_config_map" "dovecot_sieve" {
  metadata {
    name      = "dovecot-sieve"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }

  data = {
    "sieve" = file("${path.module}/resources/mail/sieve")
  }
}

resource "kubernetes_manifest" "mail_certificate" {
  manifest = {
    apiVersion = "cert-manager.io/v1"
    kind       = "Certificate"
    metadata = {
      name      = "mail-cert"
      namespace = kubernetes_namespace.mail.metadata[0].name
    }
    spec = {
      secretName = "mail-tls"
      issuerRef = {
        name = "letsencrypt-prod"
        kind = "ClusterIssuer"
      }
      commonName = "imap.${local.domain_name}"
      dnsNames   = ["imap.${local.domain_name}", "smtp.${local.domain_name}"]
    }
  }
}

module "mail_backup" {

  source = "./modules/scw_backup_job"

  namespace = kubernetes_namespace.mail.metadata[0].name
  app_name  = "mail"

  scaleway_organization_id = var.scaleway_organization_id
  scaleway_project_id      = var.scaleway_organization_id
  access_key               = scaleway_iam_api_key.backup_app_api.access_key
  secret_key               = scaleway_iam_api_key.backup_app_api.secret_key
  bucket_name              = scaleway_object_bucket.backup.name

  retention_days = local.local_backup_retention_days
  cron_schedule  = local.mail_files_backup_cron_schedule

  source_folder = local.mail_path
  target_folder = local.mail_backup_path
}

resource "kubernetes_cron_job_v1" "fetch_emails" {
  metadata {
    namespace = kubernetes_namespace.mail.metadata[0].name
    name      = "fetch-emails"
  }

  spec {
    schedule = local.fetch_emails_schedule

    job_template {
      metadata {
        name = "fetch-emails"
      }
      spec {
        backoff_limit           = 1
        parallelism             = 1
        completions             = 1
        active_deadline_seconds = 300

        template {
          metadata {
            labels = {
              app = "fetch-emails"
            }
          }

          spec {
            container {
              name  = "getmail"
              image = "a13labs/getmail6:${local.getmail_version}"
              args  = concat(["--getmaildir", "/var/getmail"], flatten([for job in local.getmail_jobs : ["-r", "/getmail/getmailrc_${job.name}"]]))

              volume_mount {
                name       = "getmail-config"
                mount_path = "/getmail"
                read_only  = true
              }

              security_context {
                run_as_user  = 1000
                run_as_group = 1000
              }

              volume_mount {
                name       = "getmail-cache"
                mount_path = "/var/getmail"
              }
            }


            restart_policy = "Never"

            volume {
              name = "getmail-config"
              config_map {
                name = "getmail-config"
              }
            }

            volume {
              name = "getmail-cache"
              host_path {
                path = local.getmail_path
              }
            }
          }
        }
      }
    }
  }
}

resource "kubernetes_deployment" "dovecot" {
  metadata {
    namespace = kubernetes_namespace.mail.metadata[0].name
    name      = "dovecot"
    labels = {
      app = "dovecot"
    }
  }
  spec {
    replicas = 1
    selector {
      match_labels = {
        app = "dovecot"
      }
    }
    strategy {
      type = "Recreate"
    }
    template {
      metadata {
        labels = {
          app = "dovecot"
        }
      }
      spec {
        service_account_name = kubernetes_service_account.mail_app.metadata[0].name
        container {
          name  = "dovecot"
          image = "dovecot/dovecot:latest"
          port {
            container_port = 31993
            host_port      = 993
          }
          port {
            container_port = 31024
          }
          port {
            container_port = 31000
          }
          volume_mount {
            name       = "email-storage"
            mount_path = "/var/mail"
          }
          volume_mount {
            name       = "users"
            mount_path = "/etc/dovecot/users"
            sub_path   = "users"
            read_only  = true
          }
          volume_mount {
            name       = "dovecot-config"
            mount_path = "/etc/dovecot/dovecot.conf"
            sub_path   = "dovecot.conf"
            read_only  = true
          }
          volume_mount {
            name       = "dovecot-sieve"
            mount_path = "/etc/dovecot/sieve/default.sieve"
            sub_path   = "sieve"
            read_only  = true
          }
          volume_mount {
            name       = "certs"
            mount_path = "/etc/letsencrypt"
            read_only  = true
          }
          volume_mount {
            name       = "mail-crypt-certs"
            mount_path = "/etc/mail-crypt"
            read_only  = true
          }
        }
        volume {
          name = "email-storage"
          host_path {
            path = local.mailboxes_path
          }
        }
        volume {
          name = "users"
          secret {
            secret_name = kubernetes_secret.dovecot_users.metadata[0].name
          }
        }
        volume {
          name = "dovecot-config"
          config_map {
            name = kubernetes_config_map.dovecot_config.metadata[0].name
          }
        }
        volume {
          name = "certs"
          secret {
            secret_name = "mail-tls"
          }
        }
        volume {
          name = "dovecot-sieve"
          config_map {
            name = kubernetes_config_map.dovecot_sieve.metadata[0].name
          }
        }
        volume {
          name = "mail-crypt-certs"
          secret {
            secret_name = kubernetes_secret.mail_crypt_certs.metadata[0].name
          }
        }
      }
    }
  }
  depends_on = [kubernetes_manifest.mail_certificate, kubernetes_service_account.mail_app]
  lifecycle {
    ignore_changes = [spec[0].template[0].metadata[0].annotations]
    replace_triggered_by = [
      kubernetes_config_map.dovecot_config,
      kubernetes_config_map.dovecot_sieve,
    ]
  }
}

resource "kubernetes_deployment" "postfix" {
  metadata {
    namespace = kubernetes_namespace.mail.metadata[0].name
    name      = "postfix"
    labels = {
      app = "postfix"
    }
  }
  spec {
    replicas = 1
    selector {
      match_labels = {
        app = "postfix"
      }
    }
    strategy {
      type = "Recreate"
    }
    template {
      metadata {
        labels = {
          app = "postfix"
        }
      }
      spec {
        service_account_name = kubernetes_service_account.mail_app.metadata[0].name
        container {
          name  = "postfix"
          image = "tozd/postfix:alpine-316"
          port {
            container_port = 465
            host_port      = 465
          }
          dynamic "env" {
            for_each = local.postfix_env_vars
            content {
              name  = env.key
              value = env.value
            }
          }
          dynamic "env" {
            for_each = local.postfix_secrets
            content {
              name = env.key
              value_from {
                secret_key_ref {
                  key  = env.value.key
                  name = env.value.name
                }
              }
            }
          }

          volume_mount {
            name       = "email-storage"
            mount_path = "/var/spool/postfix"
          }
          volume_mount {
            name       = "postfix-run-config"
            mount_path = "/etc/service/postfix/run.config"
            sub_path   = "run.config"
            read_only  = true
          }
          volume_mount {
            name       = "bcc-map"
            mount_path = "/etc/postfix/bcc_map"
            sub_path   = "bcc_map"
            read_only  = true
          }
          volume_mount {
            name       = "certs"
            mount_path = "/etc/letsencrypt"
            read_only  = true
          }
          volume_mount {
            name       = "postfix-logs"
            mount_path = "/var/log/postfix"
            read_only  = false
          }
        }
        volume {
          name = "email-storage"
          host_path {
            path = local.postfix_path
          }
        }
        volume {
          name = "postfix-run-config"
          config_map {
            name = kubernetes_config_map.postfix_config.metadata[0].name
          }
        }
        volume {
          name = "bcc-map"
          secret {
            secret_name = kubernetes_secret.bcc_map.metadata[0].name
          }
        }
        volume {
          name = "certs"
          secret {
            secret_name = "mail-tls"
          }
        }
        volume {
          name = "postfix-logs"
          host_path {
            path = "/var/log/postfix"
          }
        }
      }
    }
  }
  depends_on = [kubernetes_manifest.mail_certificate, kubernetes_service_account.mail_app]
  lifecycle {
    ignore_changes = [spec[0].template[0].metadata[0].annotations]
    replace_triggered_by = [
      kubernetes_config_map.postfix_config,
      kubernetes_secret.relay_mail_credentials,
      kubernetes_secret.bcc_map,
    ]
  }
}


resource "kubernetes_service" "dovecot" {
  metadata {
    namespace = kubernetes_namespace.mail.metadata[0].name
    name      = "dovecot"
  }

  spec {
    selector = {
      app = "dovecot"
    }

    port {
      name        = "imap"
      port        = 993
      target_port = 31993
    }

    type = "LoadBalancer"
  }

  lifecycle {
    ignore_changes = [
      metadata[0].annotations
    ]
  }
}

resource "kubernetes_service" "dovecot_lmtp" {
  metadata {
    name      = "dovecot-lmtp"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }

  spec {
    selector = {
      app = "dovecot"
    }
    port {
      port = 31024
    }
    cluster_ip = "None"
  }
}

resource "kubernetes_service" "dovecot_auth" {
  metadata {
    name      = "dovecot-auth"
    namespace = kubernetes_namespace.mail.metadata[0].name
  }

  spec {
    selector = {
      app = "dovecot"
    }
    port {
      port = 31000
    }
    cluster_ip = "None"
  }
}

resource "kubernetes_service" "postfix" {
  metadata {
    namespace = kubernetes_namespace.mail.metadata[0].name
    name      = "postfix"
  }

  spec {
    selector = {
      app = "postfix"
    }

    port {
      name        = "smtp"
      port        = 465
      target_port = 465
    }

    type = "LoadBalancer"
  }

  lifecycle {
    ignore_changes = [
      metadata[0].annotations
    ]
  }
}
