class LdapService < ApplicationService
  def initialize
    @suffix = ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
  end

  def add_attribute(dn, attr, values)
    ldap_client.add_attribute dn, attr, values
  end

  def replace_attribute(dn, attr, values)
    ldap_client.replace_attribute dn, attr, values
  end

  def delete_attribute(dn, attr)
    ldap_client.delete_attribute dn, attr
  end

  def add_entry(dn, attrs, interactive=false)
    puts "Adding entry: #{dn}" if interactive
    res = ldap_client.add dn: dn, attributes: attrs
    puts res.inspect if interactive && !res
    res
  end

  def delete_entry(dn, interactive=false)
    puts "Deleting entry: #{dn}" if interactive
    res = ldap_client.delete dn: dn
    puts res.inspect if interactive && !res
    res
  end

  def delete_all_entries!
    if Rails.env.production?
      raise "Mass deletion of entries not allowed in production"
    end

    filter  = Net::LDAP::Filter.eq("objectClass", "*")
    entries = ldap_client.search(base: @suffix, filter: filter, attributes: %w{dn})
    entries.sort_by!{ |e| e.dn.length }.reverse!

    entries.each do |e|
      delete_entry e.dn, true
    end
  end

  def fetch_users(args={})
    if args[:ou]
      treebase = "ou=#{args[:ou]},cn=users,#{@suffix}"
    else
      treebase = ldap_config["base"]
    end

    attributes = %w[
      dn cn uid mail displayName admin service
      mailRoutingAddress mailpassword
    ]
    filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*")

    entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
    entries.sort_by! { |e| e.cn[0] }
    entries = entries.collect do |e|
      {
        uid: e.uid.first,
        mail: e.try(:mail) ? e.mail.first : nil,
        display_name: e.try(:displayName) ? e.displayName.first : nil,
        admin: e.try(:admin) ? 'admin' : nil,
        service: e.try(:service),
        email_maildrop: e.try(:mailRoutingAddress),
        email_password: e.try(:mailpassword)
      }
    end
  end

  def fetch_organizations
    attributes = %w{dn ou description}
    filter     = Net::LDAP::Filter.eq("objectClass", "organizationalUnit")
    # filter     = Net::LDAP::Filter.eq("objectClass", "*")
    treebase   = "cn=users,#{@suffix}"

    entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)

    entries.sort_by! { |e| e.ou[0] }

    entries = entries.collect do |e|
      {
        dn: e.dn,
        ou: e.ou.first,
        description: e.try(:description) ? e.description.first : nil,
      }
    end
  end

  def add_organization(ou, description, interactive=false)
    dn = "ou=#{ou},cn=users,#{@suffix}"

    aci = <<-EOS
(target="ldap:///cn=*,ou=#{ou},cn=users,#{@suffix}")(targetattr="cn || sn || uid || mail || userPassword || nsRole || objectClass") (version 3.0; acl "service-#{ou.gsub(".", "-")}-read-search"; allow (read,search) userdn="ldap:///uid=service,ou=#{ou},cn=applications,#{@suffix}";)
    EOS

    attrs = {
      objectClass: ["top", "organizationalUnit"],
      description: description,
      ou: ou,
      aci: aci
    }

    add_entry dn, attrs, interactive
  end

  def reset_directory!
    if Rails.env.production?
      raise "Resetting the directory not allowed in production"
    end

    delete_all_entries!

    user_read_aci = <<-EOS
(target="ldap:///#{@suffix}")(targetattr="*") (version 3.0; acl "user-read-search-own-attributes"; allow (read,search) userdn="ldap:///self";)
    EOS

    add_entry @suffix, {
      dc: "kosmos", objectClass: ["top", "domain"], aci: user_read_aci
    }, true

    add_entry "cn=users,#{@suffix}", {
       cn: "users", objectClass: ["top", "organizationalRole"]
    }, true
  end

  private

  def ldap_client
    ldap_client ||= Net::LDAP.new host: ldap_config['host'],
      port: ldap_config['port'],
      # TODO has to be :simple_tls if TLS is enabled
      # encryption: ldap_config['ssl'],
      auth: {
        method: :simple,
        username: ldap_config['admin_user'],
        password: ldap_config['admin_password']
      }
  end

  def ldap_config
    ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
  end
end
