<?php

namespace Buckhill\LDAP\tests\units;

use mageekguy\atoum;
use Buckhill\LDAP\Entry;
use Buckhill\LDAP\EntryCollection;
use Buckhill\LDAP\Client as LDAPClient;
use Buckhill\LDAP\Errors\ConnectionError;
use Buckhill\LDAP\Errors\NoConnectionError;
use Buckhill\LDAP\Errors\BadOptionFormatError;

class Client extends atoum
{
    public function testConnectionSucceeds()
    {
        $this
            ->given($connection = $this->getValidConnection())
            ->then
                ->object($connection)->isInstanceOf(LDAPClient::class)
                ->boolean($connection->connect())->isTrue()
        ;
    }

    public function testConnectionFails()
    {
        $this
            ->given($conn = new LDAPClient('bad', 'bad'))
            ->then
                ->exception(function() use ($conn) {
                    $conn->connect();
                })
                    ->isInstanceOf(ConnectionError::class)
        ;
    }

    public function testConnectionFailsAtTLS()
    {
        $this
            ->given($conn = new LDAPClient('10.0.0.188', 389, [], true))
            ->then
                ->exception(function() use ($conn) {
                    $conn->connect();
                })
                    ->isInstanceOf(ConnectionError::class)
                    ->hasMessage('Could not start TLS for 10.0.0.188:389')
        ;
    }

    public function testConnectionFailsAtInvalidHost()
    {
        $this
            ->given($conn = new LDAPClient('Invalid IP', 389))
            ->then
                ->exception(function() use ($conn){
                    $conn->connect();
                })
                    ->isInstanceOf(ConnectionError::class)
                    ->hasMessage('Error: php_network_getaddresses: getaddrinfo failed: Name or service not known. Host: Invalid IP, port: 389')

                ->exception(function() use ($conn){
                    $conn->checkIfConnected();
                })
                    ->isInstanceOf(NoConnectionError::class)
        ;
    }


    public function testConnectionFailsAtInvalidOptions()
    {
        $this
            ->exception(function()
            {
                $conn = new LDAPClient('10.0.0.188', 389, [['name' => LDAP_OPT_CLIENT_CONTROLS, 'value' => -0001]]);

                $conn->connect();
            })
                ->isInstanceOf(ConnectionError::class)

            ->exception(function()
            {
                $conn = new LDAPClient('10.0.0.188', 389, ['bad' => 'options']);

                $conn->connect();
            })
                ->isInstanceOf(BadOptionFormatError::class)
                ->hasMessage("Bad options format. Every option must contain 'name' and 'value' pairs.")
        ;
    }

    public function testSearch()
    {
        $this
            ->given($conn = $this->getValidConnection())
            ->and($base_dn = 'ou=users,dc=buckhill,dc=co,dc=uk')
            ->and($filter = 'uid=nikola*')
            ->and($attributes = ["memberof", "cn", "dn", "mail", "uid", "givenname", "sn", "telephonenumber", "postaladdress", "o", "l", "postalcode", "st"])
            ->then
                ->boolean($conn->connect())->isTrue()
                ->array($result = $conn->search($base_dn, $filter, []))->isNotEmpty()
                ->array($result = $conn->search($base_dn, $filter, $attributes))->isNotEmpty()
                ->object($result[0])->isInstanceOf(EntryCollection::class)
                ->object($result[1])->isInstanceOf(EntryCollection::class)
                ->boolean($result[0]->has('memberof'))->isTrue()
                ->object($result[0]->get('memberof'))->isInstanceOf(Entry::class)
                ->array($result[0]->get('memberof')->getValue())->isNotEmpty()
                ->array($result[0]->toArray())->isNotEmpty()->hasKey('cn')

        ;
    }

    public function testAuthFails()
    {
        $this
            ->given($conn = $this->getValidConnection())
            ->and($conn->connect())
            ->then
                ->boolean($conn->authenticate('test', 'test'))->isFalse()
                ->string($conn->errorString())->isEqualTo('Invalid DN syntax')
                ->integer($conn->errorNumber())->isEqualTo(34)
                ->boolean($conn->authenticate('uid=test,ou=users,dc=buckhill,dc=co,dc=uk', 'this is a bad password'))->isFalse()
                ->string($conn->errorString())->isEqualTo('Invalid credentials')
                ->integer($conn->errorNumber())->isEqualTo(49)
        ;
    }

    public function testAnonymousBind()
    {
        $this
            ->given($conn = $this->getValidConnection())
            ->and($conn->connect())
            ->then
                ->boolean($conn->bind(null))->isTrue()
                ->boolean($conn->anonymous())->isTrue();
        ;
    }

    public function testAuthSucceeds()
    {
        $this
            ->given($conn = $this->getValidConnection())
            ->and($username = 'uid=test,ou=users,dc=buckhill,dc=co,dc=uk')
            ->then
                ->boolean($conn->connect())->isTrue()
                ->boolean($conn->authenticate($username, 'test'))->isTrue()
        ;
    }

    public function testEscapeFunction()
    {
        $this
            ->given($conn = $this->getValidConnection())
            ->then
                ->string($conn->escape('nikola+test@buckhill.co.uk'))->isEqualTo('nikola\+test@buckhill.co.uk')
            ;
    }

    public function testPasswordFunction()
    {
        $this
            ->given($conn = $this->getValidConnection())
            ->then
                ->string($conn->toPassword('test'))->isEqualTo('{SHA}qUqP5cyxm6YcTAhz05Hph5gvu9M=')
        ;
    }

    public function testAddAndDeleteUser()
    {
        $this
            ->given($conn = $this->getValidConnection())
            ->and($conn->connect())
            ->and($conn->bind('cn=admin,dc=buckhill,dc=co,dc=uk', 'letmein123')) // Bind as admin to local test server
            ->and($uid = 'devtest2@buckhill.co.uk')
            ->and($rdn = 'ou=users,dc=buckhill,dc=co,dc=uk')
            ->and($entry = [
                'objectclass' => [
                    'top', 'person', 'organizationalPerson', 'inetOrgPerson', 'eduPerson', 'userSecurityInformation'
                ],
                'cn'                => $uid,
                'registeredaddress' => 'Test',
                'givenname'         => 'Test',
                'sn'                => 'TheTest',
                'initials'          => 'T.T.',
                'mail'              => $uid
            ])

            ->then
                ->boolean($result = $conn->addUser($rdn, $uid, $entry, 'testPassword'))->isTrue()
                ->boolean($result = $conn->bind('uid=' . $uid .','. $rdn, 'testPassword'))->isTrue()
                ->boolean($conn->bind('cn=admin,dc=buckhill,dc=co,dc=uk', 'letmein123'))->isTrue() // bind as admin to delete the user
                ->boolean($conn->deleteUser('uid=' . $uid .','. $rdn))->isTrue()
            ;

    }
/*
    public function testDeleteUser()
    {
        $this
            ->given($conn = $this->getValidConnection())
            ->and($conn->connect())
            ->and($conn->bind('cn=admin,dc=buckhill,dc=co,dc=uk', 'letmein123')) // Bind as admin to local test server
            ->and($dn = 'cn=devtest@buckhill.co.uk,ou=users,dc=buckhill,dc=co,dc=uk')
            ->then
                ->boolean($conn->deleteUser($dn))->isTrue()
            ;
    }
*/
    /**
     * @return LDAPClient
     */
    protected function getValidConnection()
    {
        $options = [
            [
                'name' => LDAP_OPT_PROTOCOL_VERSION,
                'value' => 3
            ]
        ];

        $client = new LDAPClient('10.0.0.188', '389', $options, false);

        return $client;
    }
}