// Package ldap is a CoreDNS plugin that resolves A, AAAA y PTR RR from a ldap backend.
//
// It serves as a backend connector for autoritative zone data.
// Ldap is often used for bare metal inventories. This use is the main use case
// for this plugin. Other use cases might eventually be supported.
// fqdn and ip4 / ip6 information is mapped from it's repsective ldap schema and
// served as DNS records over coredns. Mapping is configurable. To reduce load
// on the backend, a configurable cache is bundled.
package ldap

import (
	"context"
	"errors"
	"strings"
	"sync"
	"time"
	"net"

	"github.com/coredns/coredns/plugin"
	"github.com/coredns/coredns/plugin/etcd/msg"
	"github.com/coredns/coredns/plugin/file"
	"github.com/coredns/coredns/plugin/pkg/fall"
	"github.com/coredns/coredns/plugin/pkg/upstream"
	"github.com/coredns/coredns/request"

	"github.com/miekg/dns"
	"gopkg.in/ldap.v3"
)

type ldapRecord struct {
	fqdn string
	ip   net.IP
}

func (r *ldapRecord) A() (A *dns.A) {
	return &dns.A{Hdr: dns.RR_Header{Name: r.fqdn, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0}, A: r.ip}
}

// Ldap is an ldap plugin to serve zone entries from a ldap backend.
type Ldap struct {
	Next     plugin.Handler
	Fall     fall.F
	Upstream *upstream.Upstream
	Client   ldap.Client
	Zones    file.Zones

	searchRequest *ldap.SearchRequest
	ldapURL       string
	pagingLimit   uint32
	syncInterval  time.Duration
	username      string
	password      string
	sasl          bool
	fqdnAttr      string
	ip4Attr       string
	zMu           sync.RWMutex
	ttl           time.Duration
}

// New returns an initialized Ldap with defaults.
func New(zoneNames []string) *Ldap {
	l := new(Ldap)
	l.Zones.Names = zoneNames
	l.pagingLimit = 0
	// SearchRequest defaults
	l.searchRequest = new(ldap.SearchRequest)
	l.searchRequest.DerefAliases = ldap.NeverDerefAliases // TODO: Reason
	l.searchRequest.Scope = ldap.ScopeWholeSubtree        // search whole subtree
	l.searchRequest.SizeLimit = 500                       // TODO: Reason
	l.searchRequest.TimeLimit = 500                       // TODO: Reason
	l.searchRequest.TypesOnly = false                     // TODO: Reason
	return l
}

var (
	errNoItems        = errors.New("no items found")
	errNsNotExposed   = errors.New("namespace is not exposed")
	errInvalidRequest = errors.New("invalid query name")
)

// InitClient initializes a Ldap client.
func (l *Ldap) InitClient() (err error) {
	l.Client, err = ldap.DialURL(l.ldapURL)
	if err != nil {
		log.Fatal(err)
		return err
	}
	defer l.Client.Close()
	return nil
}

