package ldapmdl

import (
	"crypto/tls"
	"errors"
	"strings"

	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/configmdl"
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/errormdl"
	"corelab.mkcl.org/MKCLOS/coredevelopmentplatform/corepkgv2/loggermdl"
	ldap "gopkg.in/ldap.v2"
)

/*
Author : RahulS
LDAP authentication module - Authenticates the user with given LDAP server
Requires 	: loginID (string), password (string)
Returns		: 0 - If any error occurs, error - respective error object. Will occur if in following conditions
					a. Blank loginID
					b. Blank password
					c. Error connecting to LDAP server
					d. Error reconnecting to LDAP using TSL
					e. Error binding admin username and password with LDAP
					f. Error searhin user on LDAP server
			  1 - If user does not exist on LDAP server, error - nil
			  2 - If multiple entries found against loginID, error - nil
			  3 - If user enters wrong password, error - nil
			  4 - Successful authentication, error - nil
*/

//LDAPConfig to get read and store LDAP server configuration
type LDAPConfig struct {
	BaseDN               string
	LDAPServerIPWithPort string
	FilterDN             string
	LDAPUsername         string
	LDAPPassword         string
}

var ldapConfig LDAPConfig

//InitLDAP get LDAP server configuration
func InitLDAP(configFilePath string) error {
	_, configError := configmdl.InitConfig(configFilePath, &ldapConfig)
	if errormdl.CheckErr(configError) != nil {
		return configError
	}
	return nil
}

//AuthenticateOnLDAP authenticates user with LDAP server
func AuthenticateOnLDAP(loginID, password string) (int, error) {

	if strings.TrimSpace(loginID) == "" {

		loggermdl.LogError("ldapmdl : loginID required")
		return 0, errors.New("ldapmdl : loginID required")

	} else if strings.TrimSpace(password) == "" {

		loggermdl.LogError("ldapmdl : password required")
		return 0, errors.New("ldapmdl : password required")

	}

	//Check if LDAP configuration is properly set through config file. (Call InitLDAP() to set configuration)
	ldapInitConfigError := CheckLDAPConfig()
	if ldapInitConfigError != nil {
		return 0, ldapInitConfigError
	}

	//Create connection to LDAP server
	ldapConnection, ldapConnectionError := ldap.Dial("tcp", ldapConfig.LDAPServerIPWithPort)
	if errormdl.CheckErr(ldapConnectionError) != nil {
		loggermdl.LogError("ldapmdl connectionError : ", ldapConnectionError)
		return 0, ldapConnectionError
	}

	defer ldapConnection.Close()

	//Reconnect with TLS(Transport Layer Security Protocol)
	startTLSError := ldapConnection.StartTLS(&tls.Config{InsecureSkipVerify: true})
	if errormdl.CheckErr1(startTLSError) != nil {
		loggermdl.LogError("ldapmdl startTLSError : ", startTLSError)
		return 0, startTLSError
	}

	//Bind with administrator user who has credentials to operation like 'search'
	ldapBindError := ldapConnection.Bind(ldapConfig.LDAPUsername, ldapConfig.LDAPPassword)
	if errormdl.CheckErr2(ldapBindError) != nil {
		loggermdl.LogInfo("ldapmdl ldapBindError: ", ldapBindError)
		return 0, ldapBindError
	}

	//Search for required username which is to be authenticated
	result, searchError := ldapConnection.Search(ldap.NewSearchRequest(
		ldapConfig.BaseDN,
		ldap.ScopeWholeSubtree,
		ldap.NeverDerefAliases,
		0,
		0,
		false,
		filter(loginID, ldapConfig.FilterDN),
		[]string{"dn"},
		nil,
	))

	//Hand search error
	if errormdl.CheckErr3(searchError) != nil {
		loggermdl.LogError("ldapmdl searchError : ", searchError)
		return 0, searchError
	}

	//Return 1 if user does not exist on LDAP server
	if len(result.Entries) < 1 {
		return 1, nil
	}

	//Return 2 if multiple entries found against one userId
	if errormdl.CheckBool(len(result.Entries) > 1) {
		return 2, nil
	}

	//Bind the password with given userId if errors occurs while binding, user has entered wrong password
	if userCredentialsBindError := ldapConnection.Bind(result.Entries[0].DN, password); userCredentialsBindError != nil {
		return 3, nil
	}

	//return 4 if authentication is successful
	loggermdl.LogDebug("ldapmdl : Authentication successful")
	return 4, nil
}

func filter(needle string, filterDN string) string {
	res := strings.Replace(filterDN, "{username}", needle, -1)
	return res
}

//CheckLDAPConfig checks of LDAP configuration is initialized or not
func CheckLDAPConfig() error {

	if strings.TrimSpace(ldapConfig.BaseDN) == "" {
		return errors.New("LDAP baseDN not found")
	} else if strings.TrimSpace(ldapConfig.FilterDN) == "" {
		return errors.New("LDAP filterDN not found")
	} else if strings.TrimSpace(ldapConfig.LDAPPassword) == "" {
		return errors.New("LDAP password not found")
	} else if strings.TrimSpace(ldapConfig.LDAPServerIPWithPort) == "" {
		return errors.New("LDAP IPAddress not found IPAdress should be with port")
	} else if strings.TrimSpace(ldapConfig.LDAPUsername) == "" {
		return errors.New("LDAP username not found")
	}
	return nil
}