//go:generate eventbus-mockery -dir . -output ../mocks -case snake -name "LDAPSearcher"
package ldap

import (
	"fmt"

	"github.com/go-ldap/ldap"
	"github.com/pkg/errors"
)

type User struct {
	CN  string
	UID string
}

type LDAPSearcher interface {
	Search(url string, req *ldap.SearchRequest) (*ldap.SearchResult, error)
}

type ldapSearcher struct{}

func (l *ldapSearcher) Search(url string, req *ldap.SearchRequest) (*ldap.SearchResult, error) {
	client, err := ldap.DialURL(url)
	if err != nil {
		return nil, errors.Wrap(err, "could not dial LDAP")
	}

	err = client.UnauthenticatedBind("")
	if err != nil {
		return nil, errors.Wrap(err, "unable to bind to LDAP")
	}

	result, err := client.Search(req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to query LDAP")
	}

	return result, nil
}

type Client struct {
	url          string
	usersBaseDN  string
	groupsBaseDN string
	ldapSearcher LDAPSearcher
}

func New(url, usersBaseDN, groupsBaseDN string) *Client {
	return &Client{
		url:          url,
		usersBaseDN:  usersBaseDN,
		groupsBaseDN: groupsBaseDN,
		ldapSearcher: &ldapSearcher{},
	}
}

func (c *Client) UsersInGroup(group string) ([]*User, error) {
	req := ldap.NewSearchRequest(
		c.usersBaseDN,
		ldap.ScopeWholeSubtree,
		0, 0, 0, false,
		fmt.Sprintf("(memberof=cn=%s,%s)", group, c.groupsBaseDN),
		[]string{"cn", "uid"},
		[]ldap.Control{})

	result, err := c.ldapSearcher.Search(c.url, req)
	if err != nil {
		return nil, errors.Wrap(err, "failed to query LDAP")
	}

	users := []*User{}
	for _, entry := range result.Entries {
		users = append(users, &User{
			CN:  entry.Attributes[0].Values[0],
			UID: entry.Attributes[1].Values[0],
		})
	}

	return users, nil
}
