package auth

import (
	"crypto/tls"
	"errors"
	"fmt"
	"log"

	ldap "gopkg.in/ldap.v3"
)

//Conn represents an Active Directory connection.
type Conn struct {
	Conn   *ldap.Conn
	Config *Config
}

//Connect returns an open connection to an Active Directory server or an error if one occurred.
func (c *Config) Connect() (*Conn, error) {
	switch c.Security {
	case SecurityNone:
		conn, err := c.ConnectClear()
		return conn, err
	case SecurityTLS:
		conn, err := c.ConnectTLS()
		return conn, err
	case SecurityStartTLS:
		conn, err := c.ConnectStartTLS()
		return conn, err
	default:
		return nil, errors.New("Configuration error: invalid SecurityType")
	}
}

//ConnectClear is a function used to connect to ldap server using clear auth
func (c *Config) ConnectClear() (*Conn, error) {
	var conn *ldap.Conn
	var err error

	for index, server := range c.Servers {
		conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", server, c.Port))
		if err != nil && index < len(c.Servers)-1 {
			log.Println("Failed dial, trying next server")
		} else if err != nil {
			log.Println("Servers are all unavailable")
			return nil, fmt.Errorf("Connection error: %v", err)
		} else {
			return &Conn{Conn: conn, Config: c}, nil
		}
	}

	return &Conn{Conn: conn, Config: c}, nil
}

//ConnectTLS is a function used to connect to ldap server using TLS
func (c *Config) ConnectTLS() (*Conn, error) {
	var conn *ldap.Conn
	var err error

	for index, server := range c.Servers {
		tlscfg := &tls.Config{ServerName: server}
		if c.SkipVerify {
			tlscfg.InsecureSkipVerify = true
		}
		conn, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", server, c.Port), tlscfg)
		if err != nil && index < len(c.Servers)-1 {
			log.Println("Failed dial, trying next server")
		} else if err != nil {
			log.Println("Servers are all unavailable")
			return nil, fmt.Errorf("Connection error: %v", err)
		} else {
			return &Conn{Conn: conn, Config: c}, nil
		}
	}

	return &Conn{Conn: conn, Config: c}, nil
}

//ConnectStartTLS is a function used to connect to ldap server using TLS
func (c *Config) ConnectStartTLS() (*Conn, error) {
	var conn *ldap.Conn
	var err error

	for index, server := range c.Servers {
		tlscfg := &tls.Config{ServerName: server}
		if c.SkipVerify {
			tlscfg.InsecureSkipVerify = true
		}
		conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", server, c.Port))
		if err != nil {
			return nil, fmt.Errorf("Connection error: %v", err)
		}
		err = conn.StartTLS(tlscfg)
		if err != nil && index < len(c.Servers)-1 {
			log.Println("Failed dial, trying next server")
		} else if err != nil {
			log.Println("Servers are all unavailable")
			return nil, fmt.Errorf("Connection error: %v", err)
		} else {
			return &Conn{Conn: conn, Config: c}, nil
		}
	}

	return &Conn{Conn: conn, Config: c}, nil
}

//Bind authenticates the connection with the given userPrincipalName and password
//and returns the result or an error if one occurred.
func (c *Conn) Bind(upn, password string) (bool, error) {
	if password == "" {
		return false, nil
	}

	err := c.Conn.Bind(upn, password)
	if err != nil {
		if e, ok := err.(*ldap.Error); ok {
			if e.ResultCode == ldap.LDAPResultInvalidCredentials {
				return false, nil
			}
		}
		return false, fmt.Errorf("Bind error (%s): %v", upn, err)
	}

	return true, nil
}
