import os
from zope.interface import Interface, implements
from twisted.internet import reactor, defer
from nevow import rend, appserver, inevow, stan, loaders, flat
from formless import annotate, webform, iformless

from ldaptor.protocols.ldap import (
    ldapclient,
    ldapsyntax,
    ldapconnector,
    distinguishedname,
)
from ldaptor import ldapfilter
from ldaptor.protocols import pureldap


class ILDAPConfig(Interface):
    """Addressbook configuration retrieval."""

    def getBaseDN(self):
        """Get the LDAP base DN, as a DistinguishedName."""

    def getServiceLocationOverrides(self):
        """
        Get the LDAP service location overrides, as a mapping of
        DistinguishedName to (host, port) tuples.
        """


class LDAPConfig:
    implements(ILDAPConfig)

    def __init__(self, baseDN, serviceLocationOverrides=None):
        self.baseDN = distinguishedname.DistinguishedName(baseDN)
        self.serviceLocationOverrides = {}
        if serviceLocationOverrides is not None:
            for k, v in serviceLocationOverrides.items():
                dn = distinguishedname.DistinguishedName(k)
                self.serviceLocationOverrides[dn] = v

    def getBaseDN(self):
        return self.baseDN

    def getServiceLocationOverrides(self):
        return self.serviceLocationOverrides


class IAddressBookSearch(annotate.TypedInterface):
    def search(
        sn=annotate.String(label="Last name"),
        givenName=annotate.String(label="First name"),
        telephoneNumber=annotate.String(),
        description=annotate.String(),
    ):
        pass

    search = annotate.autocallable(search)


class CurrentSearch:
    implements(IAddressBookSearch, inevow.IContainer)
    data = {}

    def _getSearchFilter(self):
        filters = []
        for attr, value in self.data.items():
            if value is not None:
                f = ldapfilter.parseMaybeSubstring(attr, value)
                filters.append(f)
        if not filters:
            return None
        searchFilter = pureldap.LDAPFilter_and(
            [
                pureldap.LDAPFilter_equalityMatch(
                    attributeDesc=pureldap.LDAPAttributeDescription("objectClass"),
                    assertionValue=pureldap.LDAPAssertionValue("addressbookPerson"),
                )
            ]
            + filters
        )
        return searchFilter

    def search(self, **kw):
        for k, v in kw.items():
            if v is None:
                del kw[k]
        self.data = kw
        return self

    def __nonzero__(self):
        return bool(self.data)

    def __iter__(self):
        if self.data is None:
            return
        yield from self.data.items()

    def child(self, context, name):
        if name == "searchFilter":
            return self._getSearchFilter()
        if name != "results":
            return None
        config = context.locate(ILDAPConfig)

        c = ldapconnector.LDAPClientCreator(reactor, ldapclient.LDAPClient)
        d = c.connectAnonymously(
            config.getBaseDN(), config.getServiceLocationOverrides()
        )

        def _search(proto, base, searchFilter):
            baseEntry = ldapsyntax.LDAPEntry(client=proto, dn=base)
            d = baseEntry.search(filterObject=searchFilter)
            return d

        d.addCallback(_search, config.getBaseDN(), self._getSearchFilter())
        return d


def LDAPFilterSerializer(original, context):
    return original.asText()


# TODO need to make this pretty some day.
for c in [
    pureldap.LDAPFilter_and,
    pureldap.LDAPFilter_or,
    pureldap.LDAPFilter_not,
    pureldap.LDAPFilter_substrings,
    pureldap.LDAPFilter_equalityMatch,
    pureldap.LDAPFilter_greaterOrEqual,
    pureldap.LDAPFilter_lessOrEqual,
    pureldap.LDAPFilter_approxMatch,
    pureldap.LDAPFilter_present,
    pureldap.LDAPFilter_extensibleMatch,
]:
    flat.registerFlattener(LDAPFilterSerializer, c)


class AddressBookResource(rend.Page):
    docFactory = loaders.xmlfile(
        "searchform.xhtml", templateDir=os.path.split(os.path.abspath(__file__))[0]
    )

    def configurable_(self, context):
        try:
            i = context.locate(inevow.IHand)
        except KeyError:
            i = CurrentSearch()
        return i

    def data_search(self, context, data):
        d = defer.maybeDeferred(self.locateConfigurable, context, "")

        def cb(configurable):
            return configurable.original

        d.addCallback(cb)
        return d

    def child_form_css(self, request):
        return webform.defaultCSS

    def render_input(self, context, data):
        formDefaults = context.locate(iformless.IFormDefaults)
        methodDefaults = formDefaults.getAllDefaults("search")
        conf = self.configurable_(context)
        for k, v in conf:
            methodDefaults[k] = v
        return webform.renderForms()

    def render_haveSearch(self, context, data):
        r = context.tag.allPatterns(str(bool(data)))
        return context.tag.clear()[r]

    def render_searchFilter(self, context, data):
        return data.asText()

    def render_iterateMapping(self, context, data):
        headers = context.tag.allPatterns("header")
        keyPattern = context.patternGenerator("key")
        valuePattern = context.patternGenerator("value")
        divider = context.patternGenerator("divider", default=stan.invisible)
        content = [
            (keyPattern(data=key), valuePattern(data=value), divider())
            for key, value in data.items()
        ]
        if not content:
            content = context.tag.allPatterns("empty")
        else:
            # No divider after the last thing.
            content[-1] = content[-1][:-1]
        footers = context.tag.allPatterns("footer")

        return context.tag.clear()[headers, content, footers]


def getSite(config):
    form = AddressBookResource()
    form.remember(config, ILDAPConfig)
    site = appserver.NevowSite(form)
    return site
