package auth

import (
	"encoding/json"
	"fmt"
	"strings"

	"npm/internal/entity/setting"
	"npm/internal/logger"

	ldap3 "github.com/go-ldap/ldap/v3"
	"github.com/rotisserie/eris"
)

// LDAPUser is the LDAP User
type LDAPUser struct {
	Username string `json:"username"`
	Name     string `json:"name"`
	Email    string `json:"email"`
}

// LDAPAuthenticate will use ldap to authenticate with user/pass
func LDAPAuthenticate(identity, password string) (*LDAPUser, error) {
	ldapSettings, err := setting.GetLDAPSettings()
	if err != nil {
		return nil, err
	}

	dn := strings.Replace(ldapSettings.UserDN, "{{USERNAME}}", identity, 1)
	conn, err := ldapConnect(ldapSettings.Host, dn, password)
	if err != nil {
		return nil, err
	}
	// nolint: errcheck, gosec
	defer conn.Close()
	return ldapSearchUser(conn, ldapSettings, identity)
}

// Attempt ldap connection
func ldapConnect(host, dn, password string) (*ldap3.Conn, error) {
	var conn *ldap3.Conn
	var err error

	if conn, err = ldap3.DialURL(fmt.Sprintf("ldap://%s", host)); err != nil {
		logger.Error("LdapError", err)
		return nil, err
	}

	logger.Debug("LDAP Logging in with: %s", dn)
	if err := conn.Bind(dn, password); err != nil {
		if !strings.Contains(err.Error(), "Invalid Credentials") {
			logger.Error("LDAPAuthError", err)
		}
		// nolint: gosec, errcheck
		conn.Close()
		return nil, err
	}

	logger.Debug("LDAP Login Successful")
	return conn, nil
}

func ldapSearchUser(l *ldap3.Conn, ldapSettings setting.LDAPSettings, username string) (*LDAPUser, error) {
	// Search for the given username
	searchRequest := ldap3.NewSearchRequest(
		ldapSettings.BaseDN,
		ldap3.ScopeWholeSubtree,
		ldap3.NeverDerefAliases,
		0,
		0,
		false,
		strings.Replace(ldapSettings.SelfFilter, "{{USERNAME}}", username, 1),
		nil,
		nil,
	)

	sr, err := l.Search(searchRequest)
	if err != nil {
		logger.Error("LdapError", err)
		return nil, err
	}

	if len(sr.Entries) < 1 {
		return nil, eris.New("No user found in LDAP search")
	} else if len(sr.Entries) > 1 {
		j, _ := json.Marshal(sr)
		logger.Debug("LDAP Search Results: %s", j)
		return nil, eris.Errorf("Too many LDAP results returned in LDAP search: %d", len(sr.Entries))
	}

	return &LDAPUser{
		Username: strings.ToLower(username),
		Name:     sr.Entries[0].GetAttributeValue(ldapSettings.NameProperty),
		Email:    strings.ToLower(sr.Entries[0].GetAttributeValue(ldapSettings.EmailProperty)),
	}, nil
}
