<?php

namespace Teknisa\Libs\Util;

use Teknisa\Libs\Util\Repositories;
use Teknisa\Libs\Exception\Login as LoginException;


class ActiveDirectory
{
    const DC = 'COM';
    /** @var Repositories\UserData */
    private $userDataRepository;
    /** @var Environment */
    private $environment;
    private $connectionParams;

    public function __construct(Repositories\UserData $userDataRepository, Environment $environment, array $connectionParams) {
        $this->environment = $environment;
        $this->userDataRepository = $userDataRepository;
        $this->connectionParams = $connectionParams;
    }

    /**
     * @param $username
     * @param $password
     * @param $nrOrg
     */
    public function login($username, $password, $nrOrg) {
        $ldap = $this->create_ldap_connection($username, $password, $nrOrg);
        @ldap_close($ldap);
    }

    /**
     * @param $user_name
     * @param $user_password
     * @param null $nrOrg
     * @throws \Exception
     */
    public function updatePassword($user_name, $user_password, $nrOrg = null) {
        $adminUserName = $this->getADAdmUser($nrOrg);
        $adminPassword = $this->getADAdmPassword($nrOrg);
        $user_name     = "(sAMAccountName=" . $user_name . ")";

        $ldap_conn = $this->create_ldap_connection($adminUserName, $adminPassword, $nrOrg);
        $userDn    = $this->get_user_dn($ldap_conn, $user_name, $nrOrg);
        $userData  = $this->pwd_encryption($user_password);
        $result    = ldap_mod_replace($ldap_conn, $userDn, $userData);
        @ldap_close($ldap_conn);
        if (!$result) {
            LoginException::notUpdatePasswordAD();
        }
    }

    /**
     * @param $username
     * @param $password
     * @param $nrOrg
     * @throws \Exception
     * @return resource
     */
    private function create_ldap_connection($username, $password, $nrOrg = null) {
        if(!defined('LDAP_OPT_DIAGNOSTIC_MESSAGE')) {
            define('LDAP_OPT_DIAGNOSTIC_MESSAGE', 0x0032);
        }

        $adServer = $this->getADServer($nrOrg);
        $domain   = $this->getADDomain($nrOrg);

        $ldap = ldap_connect($adServer);
        if (!$ldap) {
            LoginException::notConnectAD();
        }

        $ldapRdn = $domain . "\\" . $username;
        ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);

        $bind = @ldap_bind($ldap, $ldapRdn, $password);
        ldap_get_option($ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, $extended_error);

        if(!empty($extended_error)) {
            $this->validateLDAPError($extended_error);
        } else if (!$bind) {
            LoginException::invalidADCredentials();
        }

        return $ldap;
    }

    private function validateLDAPError($error) {
        $errno = explode(',', $error);
        if(!empty($errno[2])) {
            $errno = explode(' ', $errno[2])[2];
        } else {
            throw new \Exception($error);
        }

        switch($errno) {
            case '52e':
                LoginException::invalidADCredentials();
                break;
            case '530':
                LoginException::ldapLogonNotPermitedTime();
                break;
            case '531':
                LoginException::ldapLogonNotPermitedMachine();
                break;
            case '532':
                LoginException::ldapPasswordExpired();
                break;
            case '533':
                LoginException::ldapAccountDisabled();
                break;
            case '701':
                LoginException::ldapAccountExpired();
                break;
            case '773':
                LoginException::ldapUserMustResetPassword();
                break;
            case '775':
                LoginException::ldapAccountLocked();
                break;
            default:
                LoginException::invalidADCredentials();
                break;
        }
    }

    /**
     * @param $ldap_conn
     * @param $user_name
     * @param $nrOrg
     * @return string
     * @throws \Exception
     */
    private function get_user_dn($ldap_conn, $user_name, $nrOrg = null) {
        $domain = $this->getADDomain($nrOrg);
        $dc     = self::DC;
        $baseDN = "DC=" . $domain . ",DC=" . $dc;

        $searchResults = ldap_search($ldap_conn, $baseDN, $user_name);
        if (!is_resource($searchResults)) {
            LoginException::userNotFoundAD();
        }

        return ldap_get_dn($ldap_conn, ldap_first_entry($ldap_conn, $searchResults));
    }

    /**
     * @param $newPassword
     * @return mixed
     */
    private function pwd_encryption($newPassword) {
        $newPassword = "\"" . $newPassword . "\"";
        $len = strlen($newPassword);
        $newPass = "";
        for ($i = 0; $i < $len; $i++) {
            $newPass .= "{$newPassword[$i]}\000";
        }
        $userData["unicodePwd"] = $newPass;
        return $userData;
    }

    private function getParamSenha($nrOrg) {
        $nrOrg = $nrOrg ? $nrOrg : $this->environment->getCurrentNrOrg();
        return $this->userDataRepository->getParamSenhaByNrorg($nrOrg);
    }

    private function getADDomain($nrOrg = null) {
        $paramSenha = $this->getParamSenha($nrOrg);
        return $paramSenha ? $paramSenha['NMDOMINIOAD'] : false;
    }

    private function getADServer($nrOrg = null) {
        $paramSenha = $this->getParamSenha($nrOrg);
        return $paramSenha ? $paramSenha['DSURLSERVERAD'] : false;
    }

    private function getADAdmUser($nrOrg = null) {
        $paramSenha = $this->getParamSenha($nrOrg);
        return $paramSenha ? $paramSenha['NMUSUARIOADMAD'] : false;
    }

    private function getADAdmPassword($nrOrg = null) {
        $paramSenha = $this->getParamSenha($nrOrg);
        if($paramSenha) {
            return Utilities::zeedhiDecrypt($paramSenha['DSSENHAUSUARIOADMAD']);
        } else {
            return false;
        }

    }
}